tidy&format(all): all files formatted and relevant warnings resolved
C++ / build (pull_request) Failing after 15s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 28s
Python / lint-format (pull_request) Successful in 45s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
C++ / build (pull_request) Failing after 15s
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 28s
Python / lint-format (pull_request) Successful in 45s
Python / test (pull_request) Successful in 38s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m45s
This commit is contained in:
+3
-3
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
BasedOnStyle: LLVM
|
BasedOnStyle: LLVM
|
||||||
AccessModifierOffset: 0
|
AccessModifierOffset: 0
|
||||||
AlignAfterOpenBracket: Align
|
AlignAfterOpenBracket: AlwaysBreak
|
||||||
AlignConsecutiveAssignments: false
|
AlignConsecutiveAssignments: false
|
||||||
AlignConsecutiveDeclarations: false
|
AlignConsecutiveDeclarations: false
|
||||||
AlignOperands: Align
|
AlignOperands: Align
|
||||||
AllowAllParametersOfDeclarationOnNextLine: true
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
AllowShortFunctionsOnASingleLine: Inline
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
AllowShortIfStatementsOnASingleLine: Never
|
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||||
AlwaysBreakBeforeMultilineStrings: false
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
AlwaysBreakTemplateDeclarations: Yes
|
AlwaysBreakTemplateDeclarations: MultiLine
|
||||||
BinPackArguments: false
|
BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
BreakBeforeBraces: Attach
|
BreakBeforeBraces: Attach
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
#include "blobinvertedrect.hpp"
|
#include "blobinvertedrect.hpp"
|
||||||
#include "blobshape.hpp"
|
#include "blobshape.hpp"
|
||||||
|
|
||||||
BlobGroup::BlobGroup(QObject* parent)
|
BlobGroup::BlobGroup(QObject* parent) : QObject(parent) {}
|
||||||
: QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
BlobGroup::~BlobGroup() {
|
BlobGroup::~BlobGroup() {
|
||||||
for (auto* shape : std::as_const(m_shapes))
|
for (auto* shape : std::as_const(m_shapes))
|
||||||
@@ -14,24 +12,21 @@ BlobGroup::~BlobGroup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::setSmoothing(qreal s) {
|
void BlobGroup::setSmoothing(qreal s) {
|
||||||
if (qFuzzyCompare(m_smoothing, s))
|
if (qFuzzyCompare(m_smoothing, s)) return;
|
||||||
return;
|
|
||||||
m_smoothing = s;
|
m_smoothing = s;
|
||||||
emit smoothingChanged();
|
emit smoothingChanged();
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::setColor(const QColor& c) {
|
void BlobGroup::setColor(const QColor& c) {
|
||||||
if (m_color == c)
|
if (m_color == c) return;
|
||||||
return;
|
|
||||||
m_color = c;
|
m_color = c;
|
||||||
emit colorChanged();
|
emit colorChanged();
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::addShape(BlobShape* shape) {
|
void BlobGroup::addShape(BlobShape* shape) {
|
||||||
if (!shape || m_shapes.contains(shape))
|
if (!shape || m_shapes.contains(shape)) return;
|
||||||
return;
|
|
||||||
m_shapes.append(shape);
|
m_shapes.append(shape);
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@@ -42,15 +37,13 @@ void BlobGroup::removeShape(BlobShape* shape) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::setInvertedRect(BlobInvertedRect* rect) {
|
void BlobGroup::setInvertedRect(BlobInvertedRect* rect) {
|
||||||
if (m_invertedRect == rect)
|
if (m_invertedRect == rect) return;
|
||||||
return;
|
|
||||||
m_invertedRect = rect;
|
m_invertedRect = rect;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::clearInvertedRect(BlobInvertedRect* rect) {
|
void BlobGroup::clearInvertedRect(BlobInvertedRect* rect) {
|
||||||
if (m_invertedRect != rect)
|
if (m_invertedRect != rect) return;
|
||||||
return;
|
|
||||||
m_invertedRect = nullptr;
|
m_invertedRect = nullptr;
|
||||||
markDirty();
|
markDirty();
|
||||||
}
|
}
|
||||||
@@ -75,15 +68,19 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
|
|||||||
|
|
||||||
// Use cached padded rects to find spatial neighbors
|
// Use cached padded rects to find spatial neighbors
|
||||||
const float pad = static_cast<float>(m_smoothing) * 2.0f;
|
const float pad = static_cast<float>(m_smoothing) * 2.0f;
|
||||||
const QRectF srcRect(static_cast<double>(source->m_cachedPaddedX - pad),
|
const QRectF srcRect(
|
||||||
static_cast<double>(source->m_cachedPaddedY - pad), static_cast<double>(source->m_cachedPaddedW + pad * 2.0f),
|
static_cast<double>(source->m_cachedPaddedX - pad),
|
||||||
static_cast<double>(source->m_cachedPaddedH + pad * 2.0f));
|
static_cast<double>(source->m_cachedPaddedY - pad),
|
||||||
|
static_cast<double>(source->m_cachedPaddedW + pad * 2.0f),
|
||||||
|
static_cast<double>(source->m_cachedPaddedH + pad * 2.0f));
|
||||||
|
|
||||||
for (auto* shape : std::as_const(m_shapes)) {
|
for (auto* shape : std::as_const(m_shapes)) {
|
||||||
if (shape == source)
|
if (shape == source) continue;
|
||||||
continue;
|
const QRectF otherRect(
|
||||||
const QRectF otherRect(static_cast<double>(shape->m_cachedPaddedX), static_cast<double>(shape->m_cachedPaddedY),
|
static_cast<double>(shape->m_cachedPaddedX),
|
||||||
static_cast<double>(shape->m_cachedPaddedW), static_cast<double>(shape->m_cachedPaddedH));
|
static_cast<double>(shape->m_cachedPaddedY),
|
||||||
|
static_cast<double>(shape->m_cachedPaddedW),
|
||||||
|
static_cast<double>(shape->m_cachedPaddedH));
|
||||||
if (srcRect.intersects(otherRect)) {
|
if (srcRect.intersects(otherRect)) {
|
||||||
shape->polish();
|
shape->polish();
|
||||||
shape->update();
|
shape->update();
|
||||||
@@ -97,8 +94,7 @@ void BlobGroup::markShapeDirty(BlobShape* source) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobGroup::ensurePhysicsUpdated() {
|
void BlobGroup::ensurePhysicsUpdated() {
|
||||||
if (m_physicsUpdated)
|
if (m_physicsUpdated) return;
|
||||||
return;
|
|
||||||
m_physicsUpdated = true;
|
m_physicsUpdated = true;
|
||||||
for (auto* shape : std::as_const(m_shapes))
|
for (auto* shape : std::as_const(m_shapes))
|
||||||
shape->updatePhysics();
|
shape->updatePhysics();
|
||||||
|
|||||||
@@ -9,53 +9,49 @@ class BlobShape;
|
|||||||
class BlobInvertedRect;
|
class BlobInvertedRect;
|
||||||
|
|
||||||
class BlobGroup : public QObject {
|
class BlobGroup : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
qreal smoothing READ smoothing WRITE setSmoothing NOTIFY
|
||||||
|
smoothingChanged)
|
||||||
|
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BlobGroup(QObject* parent = nullptr);
|
explicit BlobGroup(QObject* parent = nullptr);
|
||||||
~BlobGroup() override;
|
~BlobGroup() override;
|
||||||
|
|
||||||
[[nodiscard]] qreal smoothing() const {
|
[[nodiscard]] qreal smoothing() const { return m_smoothing; }
|
||||||
return m_smoothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSmoothing(qreal s);
|
void setSmoothing(qreal s);
|
||||||
|
|
||||||
[[nodiscard]] QColor color() const {
|
[[nodiscard]] QColor color() const { return m_color; }
|
||||||
return m_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setColor(const QColor& c);
|
void setColor(const QColor& c);
|
||||||
|
|
||||||
void addShape(BlobShape* shape);
|
void addShape(BlobShape* shape);
|
||||||
void removeShape(BlobShape* shape);
|
void removeShape(BlobShape* shape);
|
||||||
|
|
||||||
void setInvertedRect(BlobInvertedRect* rect);
|
void setInvertedRect(BlobInvertedRect* rect);
|
||||||
void clearInvertedRect(BlobInvertedRect* rect);
|
void clearInvertedRect(BlobInvertedRect* rect);
|
||||||
|
|
||||||
[[nodiscard]] const QList<BlobShape*>& shapes() const {
|
[[nodiscard]] const QList<BlobShape*>& shapes() const { return m_shapes; }
|
||||||
return m_shapes;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] BlobInvertedRect* invertedRect() const {
|
[[nodiscard]] BlobInvertedRect* invertedRect() const {
|
||||||
return m_invertedRect;
|
return m_invertedRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
void markDirty();
|
void markDirty();
|
||||||
void markShapeDirty(BlobShape* source);
|
void markShapeDirty(BlobShape* source);
|
||||||
void ensurePhysicsUpdated();
|
void ensurePhysicsUpdated();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void smoothingChanged();
|
void smoothingChanged();
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qreal m_smoothing = 32.0;
|
qreal m_smoothing = 32.0;
|
||||||
QColor m_color{ 0x44, 0x88, 0xff };
|
QColor m_color{0x44, 0x88, 0xff};
|
||||||
QList<BlobShape*> m_shapes;
|
QList<BlobShape*> m_shapes;
|
||||||
BlobInvertedRect* m_invertedRect = nullptr;
|
BlobInvertedRect* m_invertedRect = nullptr;
|
||||||
bool m_physicsUpdated = false;
|
bool m_physicsUpdated = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,9 @@
|
|||||||
#include <qsggeometry.h>
|
#include <qsggeometry.h>
|
||||||
#include <qsgnode.h>
|
#include <qsgnode.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent)
|
BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {}
|
||||||
: BlobShape(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setFrameIndices(quint16* idx) {
|
static void setFrameIndices(quint16* idx) {
|
||||||
// Top strip: 0-1-4, 1-5-4
|
// Top strip: 0-1-4, 1-5-4
|
||||||
@@ -43,7 +40,8 @@ static void setFrameIndices(quint16* idx) {
|
|||||||
idx[23] = 7;
|
idx[23] = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
QSGNode* BlobInvertedRect::updatePaintNode(
|
||||||
|
QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||||
if (!m_group) {
|
if (!m_group) {
|
||||||
delete oldNode;
|
delete oldNode;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -71,8 +69,11 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData
|
|||||||
delete oldNode;
|
delete oldNode;
|
||||||
node = new QSGGeometryNode;
|
node = new QSGGeometryNode;
|
||||||
|
|
||||||
auto* geometry =
|
auto* geometry = new QSGGeometry(
|
||||||
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, QSGGeometry::UnsignedShortType);
|
QSGGeometry::defaultAttributes_TexturedPoint2D(),
|
||||||
|
8,
|
||||||
|
24,
|
||||||
|
QSGGeometry::UnsignedShortType);
|
||||||
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
|
geometry->setDrawingMode(QSGGeometry::DrawTriangles);
|
||||||
node->setGeometry(geometry);
|
node->setGeometry(geometry);
|
||||||
node->setFlag(QSGNode::OwnsGeometry);
|
node->setFlag(QSGNode::OwnsGeometry);
|
||||||
@@ -120,10 +121,17 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData
|
|||||||
material->m_color = m_group->color();
|
material->m_color = m_group->color();
|
||||||
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
|
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
|
||||||
material->m_invertedRadius = m_cachedInvertedRadius;
|
material->m_invertedRadius = m_cachedInvertedRadius;
|
||||||
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter));
|
memcpy(
|
||||||
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner));
|
material->m_invertedOuter,
|
||||||
|
m_cachedInvertedOuter,
|
||||||
|
sizeof(m_cachedInvertedOuter));
|
||||||
|
memcpy(
|
||||||
|
material->m_invertedInner,
|
||||||
|
m_cachedInvertedInner,
|
||||||
|
sizeof(m_cachedInvertedInner));
|
||||||
|
|
||||||
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
const int count =
|
||||||
|
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
||||||
material->m_rectCount = count;
|
material->m_rectCount = count;
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < count; ++i)
|
||||||
material->m_rects[i] = m_cachedRects[i];
|
material->m_rects[i] = m_cachedRects[i];
|
||||||
@@ -134,52 +142,41 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData
|
|||||||
}
|
}
|
||||||
|
|
||||||
BlobInvertedRect::~BlobInvertedRect() {
|
BlobInvertedRect::~BlobInvertedRect() {
|
||||||
if (m_group)
|
if (m_group) m_group->clearInvertedRect(this);
|
||||||
m_group->clearInvertedRect(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobInvertedRect::setBorderLeft(qreal v) {
|
void BlobInvertedRect::setBorderLeft(qreal v) {
|
||||||
if (qFuzzyCompare(m_borderLeft, v))
|
if (qFuzzyCompare(m_borderLeft, v)) return;
|
||||||
return;
|
|
||||||
m_borderLeft = v;
|
m_borderLeft = v;
|
||||||
emit borderLeftChanged();
|
emit borderLeftChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobInvertedRect::setBorderRight(qreal v) {
|
void BlobInvertedRect::setBorderRight(qreal v) {
|
||||||
if (qFuzzyCompare(m_borderRight, v))
|
if (qFuzzyCompare(m_borderRight, v)) return;
|
||||||
return;
|
|
||||||
m_borderRight = v;
|
m_borderRight = v;
|
||||||
emit borderRightChanged();
|
emit borderRightChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobInvertedRect::setBorderTop(qreal v) {
|
void BlobInvertedRect::setBorderTop(qreal v) {
|
||||||
if (qFuzzyCompare(m_borderTop, v))
|
if (qFuzzyCompare(m_borderTop, v)) return;
|
||||||
return;
|
|
||||||
m_borderTop = v;
|
m_borderTop = v;
|
||||||
emit borderTopChanged();
|
emit borderTopChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobInvertedRect::setBorderBottom(qreal v) {
|
void BlobInvertedRect::setBorderBottom(qreal v) {
|
||||||
if (qFuzzyCompare(m_borderBottom, v))
|
if (qFuzzyCompare(m_borderBottom, v)) return;
|
||||||
return;
|
|
||||||
m_borderBottom = v;
|
m_borderBottom = v;
|
||||||
emit borderBottomChanged();
|
emit borderBottomChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobInvertedRect::registerWithGroup() {
|
void BlobInvertedRect::registerWithGroup() {
|
||||||
if (m_group)
|
if (m_group) m_group->setInvertedRect(this);
|
||||||
m_group->setInvertedRect(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobInvertedRect::unregisterFromGroup() {
|
void BlobInvertedRect::unregisterFromGroup() {
|
||||||
if (m_group)
|
if (m_group) m_group->clearInvertedRect(this);
|
||||||
m_group->clearInvertedRect(this);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,60 +5,58 @@
|
|||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
|
|
||||||
class BlobInvertedRect : public BlobShape {
|
class BlobInvertedRect : public BlobShape {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY borderLeftChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY borderRightChanged)
|
qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY
|
||||||
Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY borderTopChanged)
|
borderLeftChanged)
|
||||||
Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY borderBottomChanged)
|
Q_PROPERTY(
|
||||||
|
qreal borderRight READ borderRight WRITE setBorderRight NOTIFY
|
||||||
|
borderRightChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal borderTop READ borderTop WRITE setBorderTop NOTIFY
|
||||||
|
borderTopChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY
|
||||||
|
borderBottomChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BlobInvertedRect(QQuickItem* parent = nullptr);
|
explicit BlobInvertedRect(QQuickItem* parent = nullptr);
|
||||||
~BlobInvertedRect() override;
|
~BlobInvertedRect() override;
|
||||||
|
|
||||||
[[nodiscard]] qreal borderLeft() const {
|
[[nodiscard]] qreal borderLeft() const { return m_borderLeft; }
|
||||||
return m_borderLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBorderLeft(qreal v);
|
void setBorderLeft(qreal v);
|
||||||
|
|
||||||
[[nodiscard]] qreal borderRight() const {
|
[[nodiscard]] qreal borderRight() const { return m_borderRight; }
|
||||||
return m_borderRight;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBorderRight(qreal v);
|
void setBorderRight(qreal v);
|
||||||
|
|
||||||
[[nodiscard]] qreal borderTop() const {
|
[[nodiscard]] qreal borderTop() const { return m_borderTop; }
|
||||||
return m_borderTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBorderTop(qreal v);
|
void setBorderTop(qreal v);
|
||||||
|
|
||||||
[[nodiscard]] qreal borderBottom() const {
|
[[nodiscard]] qreal borderBottom() const { return m_borderBottom; }
|
||||||
return m_borderBottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBorderBottom(qreal v);
|
void setBorderBottom(qreal v);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void borderLeftChanged();
|
void borderLeftChanged();
|
||||||
void borderRightChanged();
|
void borderRightChanged();
|
||||||
void borderTopChanged();
|
void borderTopChanged();
|
||||||
void borderBottomChanged();
|
void borderBottomChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
[[nodiscard]] bool isInvertedRect() const override {
|
[[nodiscard]] bool isInvertedRect() const override { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
|
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
|
||||||
|
|
||||||
void registerWithGroup() override;
|
void registerWithGroup() override;
|
||||||
void unregisterFromGroup() override;
|
void unregisterFromGroup() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qreal m_borderLeft = 0;
|
qreal m_borderLeft = 0;
|
||||||
qreal m_borderRight = 0;
|
qreal m_borderRight = 0;
|
||||||
qreal m_borderTop = 0;
|
qreal m_borderTop = 0;
|
||||||
qreal m_borderBottom = 0;
|
qreal m_borderBottom = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
#include "blobmaterial.hpp"
|
#include "blobmaterial.hpp"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
|
static_assert(
|
||||||
"BlobMaterial packs excludeMask into a float slot via memcpy");
|
sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
|
||||||
|
"BlobMaterial packs excludeMask into a float slot via memcpy");
|
||||||
|
|
||||||
QSGMaterialType* BlobMaterial::type() const {
|
QSGMaterialType* BlobMaterial::type() const {
|
||||||
static QSGMaterialType s_type;
|
static QSGMaterialType s_type;
|
||||||
return &s_type;
|
return &s_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSGMaterialShader* BlobMaterial::createShader(QSGRendererInterface::RenderMode) const {
|
QSGMaterialShader* BlobMaterial::createShader(
|
||||||
|
QSGRendererInterface::RenderMode) const {
|
||||||
return new BlobMaterialShader;
|
return new BlobMaterialShader;
|
||||||
}
|
}
|
||||||
|
|
||||||
int BlobMaterial::compare(const QSGMaterial* other) const {
|
int BlobMaterial::compare(const QSGMaterial* other) const {
|
||||||
if (this < other)
|
if (this < other) return -1;
|
||||||
return -1;
|
if (this > other) return 1;
|
||||||
if (this > other)
|
|
||||||
return 1;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +29,8 @@ BlobMaterialShader::BlobMaterialShader() {
|
|||||||
setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb"));
|
setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb"));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
|
bool BlobMaterialShader::updateUniformData(
|
||||||
|
RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) {
|
||||||
Q_UNUSED(oldMaterial);
|
Q_UNUSED(oldMaterial);
|
||||||
auto* mat = static_cast<BlobMaterial*>(newMaterial);
|
auto* mat = static_cast<BlobMaterial*>(newMaterial);
|
||||||
QByteArray* buf = state.uniformData();
|
QByteArray* buf = state.uniformData();
|
||||||
@@ -59,10 +62,10 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
|
|||||||
|
|
||||||
// Color as vec4 (offset 96, 16 bytes)
|
// Color as vec4 (offset 96, 16 bytes)
|
||||||
const float color[4] = {
|
const float color[4] = {
|
||||||
static_cast<float>(mat->m_color.redF()),
|
mat->m_color.redF(),
|
||||||
static_cast<float>(mat->m_color.greenF()),
|
mat->m_color.greenF(),
|
||||||
static_cast<float>(mat->m_color.blueF()),
|
mat->m_color.blueF(),
|
||||||
static_cast<float>(mat->m_color.alphaF()),
|
mat->m_color.alphaF(),
|
||||||
};
|
};
|
||||||
memcpy(buf->data() + 96, color, 16);
|
memcpy(buf->data() + 96, color, 16);
|
||||||
|
|
||||||
@@ -86,11 +89,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
|
|||||||
const auto& r = mat->m_rects[i];
|
const auto& r = mat->m_rects[i];
|
||||||
const int base = 160 + i * 80;
|
const int base = 160 + i * 80;
|
||||||
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
|
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
|
||||||
float maskAsFloat;
|
float maskAsFloat = NAN;
|
||||||
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
|
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
|
||||||
const float d0[4] = { r.cx, r.cy, r.hw, r.hh };
|
const float d0[4] = {r.cx, r.cy, r.hw, r.hh};
|
||||||
const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig };
|
const float d1[4] = {maskAsFloat, r.offsetX, r.offsetY, r.minEig};
|
||||||
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
|
const float d3[4] = {r.screenHalfX, r.screenHalfY, 0.0f, 0.0f};
|
||||||
memcpy(buf->data() + base, d0, 16);
|
memcpy(buf->data() + base, d0, 16);
|
||||||
memcpy(buf->data() + base + 16, d1, 16);
|
memcpy(buf->data() + base + 16, d1, 16);
|
||||||
memcpy(buf->data() + base + 32, r.invDeform, 16);
|
memcpy(buf->data() + base + 32, r.invDeform, 16);
|
||||||
|
|||||||
@@ -9,39 +9,43 @@ struct BlobRectData {
|
|||||||
float offsetX = 0, offsetY = 0;
|
float offsetX = 0, offsetY = 0;
|
||||||
float minEig = 1.0f;
|
float minEig = 1.0f;
|
||||||
// Inverse of 2x2 deformation matrix, column-major for GLSL
|
// Inverse of 2x2 deformation matrix, column-major for GLSL
|
||||||
float invDeform[4] = { 1, 0, 0, 1 };
|
float invDeform[4] = {1, 0, 0, 1};
|
||||||
// Screen-space AABB half-extents of the deformed rect
|
// Screen-space AABB half-extents of the deformed rect
|
||||||
float screenHalfX = 0, screenHalfY = 0;
|
float screenHalfX = 0, screenHalfY = 0;
|
||||||
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
|
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
|
||||||
float radius[4] = { 0, 0, 0, 0 };
|
float radius[4] = {0, 0, 0, 0};
|
||||||
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
|
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
|
||||||
// Used by the shader to skip smin between excluded pairs.
|
// Used by the shader to skip smin between excluded pairs.
|
||||||
int excludeMask = 0;
|
int excludeMask = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlobMaterial : public QSGMaterial {
|
class BlobMaterial : public QSGMaterial {
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] QSGMaterialType* type() const override;
|
[[nodiscard]] QSGMaterialType* type() const override;
|
||||||
[[nodiscard]] QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override;
|
[[nodiscard]] QSGMaterialShader* createShader(
|
||||||
int compare(const QSGMaterial* other) const override;
|
QSGRendererInterface::RenderMode) const override;
|
||||||
|
int compare(const QSGMaterial* other) const override;
|
||||||
|
|
||||||
float m_paddedX = 0;
|
float m_paddedX = 0;
|
||||||
float m_paddedY = 0;
|
float m_paddedY = 0;
|
||||||
float m_paddedW = 0;
|
float m_paddedW = 0;
|
||||||
float m_paddedH = 0;
|
float m_paddedH = 0;
|
||||||
float m_smoothFactor = 32.0f;
|
float m_smoothFactor = 32.0f;
|
||||||
int m_rectCount = 0;
|
int m_rectCount = 0;
|
||||||
int m_myIndex = -2;
|
int m_myIndex = -2;
|
||||||
QColor m_color{ 0x44, 0x88, 0xff };
|
QColor m_color{0x44, 0x88, 0xff};
|
||||||
int m_hasInverted = 0;
|
int m_hasInverted = 0;
|
||||||
float m_invertedRadius = 0;
|
float m_invertedRadius = 0;
|
||||||
float m_invertedOuter[4] = {};
|
float m_invertedOuter[4] = {};
|
||||||
float m_invertedInner[4] = {};
|
float m_invertedInner[4] = {};
|
||||||
BlobRectData m_rects[16] = {};
|
BlobRectData m_rects[16] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
class BlobMaterialShader : public QSGMaterialShader {
|
class BlobMaterialShader : public QSGMaterialShader {
|
||||||
public:
|
public:
|
||||||
BlobMaterialShader();
|
BlobMaterialShader();
|
||||||
bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override;
|
bool updateUniformData(
|
||||||
|
RenderState& state,
|
||||||
|
QSGMaterial* newMaterial,
|
||||||
|
QSGMaterial* oldMaterial) override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,19 +4,18 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
BlobRect::BlobRect(QQuickItem* parent)
|
BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {}
|
||||||
: BlobShape(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
BlobRect::~BlobRect() {
|
BlobRect::~BlobRect() {
|
||||||
if (m_group)
|
if (m_group) m_group->removeShape(this);
|
||||||
m_group->removeShape(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::updatePolish() {
|
void BlobRect::updatePolish() {
|
||||||
if (m_physicsActive) {
|
if (m_physicsActive) {
|
||||||
float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
|
float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) +
|
||||||
float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
|
std::abs(m_dm11 - 1.0f);
|
||||||
|
float totalVel =
|
||||||
|
std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
|
||||||
|
|
||||||
if (totalDelta < 0.004f && totalVel < 0.05f) {
|
if (totalDelta < 0.004f && totalVel < 0.05f) {
|
||||||
m_dm00 = 1.0f;
|
m_dm00 = 1.0f;
|
||||||
@@ -32,18 +31,16 @@ void BlobRect::updatePolish() {
|
|||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
this,
|
this,
|
||||||
[this]() {
|
[this]() {
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
},
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
this,
|
this,
|
||||||
[this]() {
|
[this]() {
|
||||||
if (m_physicsActive && m_group)
|
if (m_physicsActive && m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
},
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,20 +61,20 @@ void BlobRect::updatePhysics() {
|
|||||||
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
|
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
|
||||||
if (dt > 0.1f || dt < 0.001f) {
|
if (dt > 0.1f || dt < 0.001f) {
|
||||||
m_prevScenePos = scenePos;
|
m_prevScenePos = scenePos;
|
||||||
if (m_physicsActive)
|
if (m_physicsActive) checkAtRest(0.0f);
|
||||||
checkAtRest(0.0f);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float velX = static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
|
const float velX =
|
||||||
const float velY = static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
|
static_cast<float>(scenePos.x() - m_prevScenePos.x()) / dt;
|
||||||
|
const float velY =
|
||||||
|
static_cast<float>(scenePos.y() - m_prevScenePos.y()) / dt;
|
||||||
m_prevScenePos = scenePos;
|
m_prevScenePos = scenePos;
|
||||||
|
|
||||||
const float speed = std::sqrt(velX * velX + velY * velY);
|
const float speed = std::sqrt(velX * velX + velY * velY);
|
||||||
|
|
||||||
if (!m_physicsActive) {
|
if (!m_physicsActive) {
|
||||||
if (speed < 5.0f)
|
if (speed < 5.0f) return;
|
||||||
return;
|
|
||||||
m_physicsActive = true;
|
m_physicsActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +86,8 @@ void BlobRect::updatePhysics() {
|
|||||||
float target11 = 1.0f;
|
float target11 = 1.0f;
|
||||||
|
|
||||||
if (speed > 5.0f) {
|
if (speed > 5.0f) {
|
||||||
const float targetStretch = 1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
const float targetStretch =
|
||||||
|
1.0f + std::min(speed * kStretchFactor, kMaxStretch);
|
||||||
const float targetCompress = 1.0f / targetStretch;
|
const float targetCompress = 1.0f / targetStretch;
|
||||||
|
|
||||||
const float cosA = velX / speed;
|
const float cosA = velX / speed;
|
||||||
@@ -106,19 +104,23 @@ void BlobRect::updatePhysics() {
|
|||||||
const float kStiffness = static_cast<float>(m_stiffness);
|
const float kStiffness = static_cast<float>(m_stiffness);
|
||||||
const float kDamping = static_cast<float>(m_damping);
|
const float kDamping = static_cast<float>(m_damping);
|
||||||
|
|
||||||
const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
|
const float accel00 =
|
||||||
|
-kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00;
|
||||||
m_dmVel00 += accel00 * dt;
|
m_dmVel00 += accel00 * dt;
|
||||||
m_dm00 += m_dmVel00 * dt;
|
m_dm00 += m_dmVel00 * dt;
|
||||||
|
|
||||||
const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
|
const float accel01 =
|
||||||
|
-kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01;
|
||||||
m_dmVel01 += accel01 * dt;
|
m_dmVel01 += accel01 * dt;
|
||||||
m_dm01 += m_dmVel01 * dt;
|
m_dm01 += m_dmVel01 * dt;
|
||||||
|
|
||||||
const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
|
const float accel11 =
|
||||||
|
-kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11;
|
||||||
m_dmVel11 += accel11 * dt;
|
m_dmVel11 += accel11 * dt;
|
||||||
m_dm11 += m_dmVel11 * dt;
|
m_dm11 += m_dmVel11 * dt;
|
||||||
|
|
||||||
m_deformMatrix = QMatrix4x4(m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
m_deformMatrix = QMatrix4x4(
|
||||||
|
m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
|
||||||
emit rawDeformMatrixChanged();
|
emit rawDeformMatrixChanged();
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
|
|
||||||
@@ -129,8 +131,7 @@ void BlobRect::setTopLeftRadius(qreal r) {
|
|||||||
if (!qFuzzyCompare(m_topLeftRadius, r)) {
|
if (!qFuzzyCompare(m_topLeftRadius, r)) {
|
||||||
m_topLeftRadius = r;
|
m_topLeftRadius = r;
|
||||||
emit topLeftRadiusChanged();
|
emit topLeftRadiusChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,8 +139,7 @@ void BlobRect::setTopRightRadius(qreal r) {
|
|||||||
if (!qFuzzyCompare(m_topRightRadius, r)) {
|
if (!qFuzzyCompare(m_topRightRadius, r)) {
|
||||||
m_topRightRadius = r;
|
m_topRightRadius = r;
|
||||||
emit topRightRadiusChanged();
|
emit topRightRadiusChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,8 +147,7 @@ void BlobRect::setBottomLeftRadius(qreal r) {
|
|||||||
if (!qFuzzyCompare(m_bottomLeftRadius, r)) {
|
if (!qFuzzyCompare(m_bottomLeftRadius, r)) {
|
||||||
m_bottomLeftRadius = r;
|
m_bottomLeftRadius = r;
|
||||||
emit bottomLeftRadiusChanged();
|
emit bottomLeftRadiusChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,38 +155,51 @@ void BlobRect::setBottomRightRadius(qreal r) {
|
|||||||
if (!qFuzzyCompare(m_bottomRightRadius, r)) {
|
if (!qFuzzyCompare(m_bottomRightRadius, r)) {
|
||||||
m_bottomRightRadius = r;
|
m_bottomRightRadius = r;
|
||||||
emit bottomRightRadiusChanged();
|
emit bottomRightRadiusChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::cornerRadii(float out[4]) const {
|
void BlobRect::cornerRadii(float out[4]) const {
|
||||||
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
const auto maxR = static_cast<float>(std::min(width(), height())) * 0.5f;
|
||||||
const auto base = std::min(static_cast<float>(m_radius), maxR);
|
const auto base = std::min(static_cast<float>(m_radius), maxR);
|
||||||
out[0] = std::min(m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base, maxR);
|
out[0] = std::min(
|
||||||
out[1] = std::min(m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius) : base, maxR);
|
m_topRightRadius >= 0 ? static_cast<float>(m_topRightRadius) : base,
|
||||||
out[2] = std::min(m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base, maxR);
|
maxR);
|
||||||
out[3] = std::min(m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base, maxR);
|
out[1] = std::min(
|
||||||
|
m_bottomRightRadius >= 0 ? static_cast<float>(m_bottomRightRadius)
|
||||||
|
: base,
|
||||||
|
maxR);
|
||||||
|
out[2] = std::min(
|
||||||
|
m_bottomLeftRadius >= 0 ? static_cast<float>(m_bottomLeftRadius) : base,
|
||||||
|
maxR);
|
||||||
|
out[3] = std::min(
|
||||||
|
m_topLeftRadius >= 0 ? static_cast<float>(m_topLeftRadius) : base,
|
||||||
|
maxR);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BlobRect::isExcluded(const BlobShape* other) const {
|
bool BlobRect::isExcluded(const BlobShape* other) const {
|
||||||
for (const auto& ptr : m_exclude) {
|
for (const auto& ptr : m_exclude) {
|
||||||
if (ptr == other)
|
if (ptr == other) return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QQmlListProperty<BlobRect> BlobRect::exclude() {
|
QQmlListProperty<BlobRect> BlobRect::exclude() {
|
||||||
return QQmlListProperty<BlobRect>(
|
return QQmlListProperty<BlobRect>(
|
||||||
this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast);
|
this,
|
||||||
|
nullptr,
|
||||||
|
&excludeAppend,
|
||||||
|
&excludeCount,
|
||||||
|
&excludeAt,
|
||||||
|
&excludeClear,
|
||||||
|
&excludeReplace,
|
||||||
|
&excludeRemoveLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
|
void BlobRect::excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect) {
|
||||||
auto* self = static_cast<BlobRect*>(prop->object);
|
auto* self = static_cast<BlobRect*>(prop->object);
|
||||||
self->m_exclude.append(rect);
|
self->m_exclude.append(rect);
|
||||||
if (self->m_group)
|
if (self->m_group) self->m_group->markDirty();
|
||||||
self->m_group->markDirty();
|
|
||||||
emit self->excludeChanged();
|
emit self->excludeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,44 +208,43 @@ qsizetype BlobRect::excludeCount(QQmlListProperty<BlobRect>* prop) {
|
|||||||
return self->m_exclude.size();
|
return self->m_exclude.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
BlobRect* BlobRect::excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index) {
|
BlobRect* BlobRect::excludeAt(
|
||||||
|
QQmlListProperty<BlobRect>* prop, qsizetype index) {
|
||||||
auto* self = static_cast<BlobRect*>(prop->object);
|
auto* self = static_cast<BlobRect*>(prop->object);
|
||||||
return self->m_exclude.at(index);
|
return self->m_exclude.at(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::excludeClear(QQmlListProperty<BlobRect>* prop) {
|
void BlobRect::excludeClear(QQmlListProperty<BlobRect>* prop) {
|
||||||
auto* self = static_cast<BlobRect*>(prop->object);
|
auto* self = static_cast<BlobRect*>(prop->object);
|
||||||
if (self->m_exclude.isEmpty())
|
if (self->m_exclude.isEmpty()) return;
|
||||||
return;
|
|
||||||
self->m_exclude.clear();
|
self->m_exclude.clear();
|
||||||
if (self->m_group)
|
if (self->m_group) self->m_group->markDirty();
|
||||||
self->m_group->markDirty();
|
|
||||||
emit self->excludeChanged();
|
emit self->excludeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
|
void BlobRect::excludeReplace(
|
||||||
|
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect) {
|
||||||
auto* self = static_cast<BlobRect*>(prop->object);
|
auto* self = static_cast<BlobRect*>(prop->object);
|
||||||
self->m_exclude[index] = rect;
|
self->m_exclude[index] = rect;
|
||||||
if (self->m_group)
|
if (self->m_group) self->m_group->markDirty();
|
||||||
self->m_group->markDirty();
|
|
||||||
emit self->excludeChanged();
|
emit self->excludeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
void BlobRect::excludeRemoveLast(QQmlListProperty<BlobRect>* prop) {
|
||||||
auto* self = static_cast<BlobRect*>(prop->object);
|
auto* self = static_cast<BlobRect*>(prop->object);
|
||||||
if (self->m_exclude.isEmpty())
|
if (self->m_exclude.isEmpty()) return;
|
||||||
return;
|
|
||||||
self->m_exclude.removeLast();
|
self->m_exclude.removeLast();
|
||||||
if (self->m_group)
|
if (self->m_group) self->m_group->markDirty();
|
||||||
self->m_group->markDirty();
|
|
||||||
emit self->excludeChanged();
|
emit self->excludeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobRect::checkAtRest(float speed) {
|
void BlobRect::checkAtRest(float speed) {
|
||||||
constexpr float kEpsilon = 0.002f;
|
constexpr float kEpsilon = 0.002f;
|
||||||
const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
|
const bool atRest =
|
||||||
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
|
std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon &&
|
||||||
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && speed < 5.0f;
|
std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon &&
|
||||||
|
std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon &&
|
||||||
|
speed < 5.0f;
|
||||||
|
|
||||||
if (atRest) {
|
if (atRest) {
|
||||||
m_dm00 = 1.0f;
|
m_dm00 = 1.0f;
|
||||||
@@ -251,9 +262,8 @@ void BlobRect::checkAtRest(float speed) {
|
|||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
this,
|
this,
|
||||||
[this]() {
|
[this]() {
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
},
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,133 +8,135 @@
|
|||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
|
|
||||||
class BlobRect : public BlobShape {
|
class BlobRect : public BlobShape {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
qreal stiffness READ stiffness WRITE setStiffness NOTIFY
|
||||||
Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged)
|
stiffnessChanged)
|
||||||
Q_PROPERTY(QQmlListProperty<BlobRect> exclude READ exclude NOTIFY excludeChanged)
|
Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged)
|
||||||
Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged)
|
qreal deformScale READ deformScale WRITE setDeformScale NOTIFY
|
||||||
Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged)
|
deformScaleChanged)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged)
|
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)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BlobRect(QQuickItem* parent = nullptr);
|
explicit BlobRect(QQuickItem* parent = nullptr);
|
||||||
~BlobRect() override;
|
~BlobRect() override;
|
||||||
|
|
||||||
[[nodiscard]] qreal stiffness() const {
|
[[nodiscard]] qreal stiffness() const { return m_stiffness; }
|
||||||
return m_stiffness;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStiffness(qreal s) {
|
void setStiffness(qreal s) {
|
||||||
if (!qFuzzyCompare(m_stiffness, s)) {
|
if (!qFuzzyCompare(m_stiffness, s)) {
|
||||||
m_stiffness = s;
|
m_stiffness = s;
|
||||||
emit stiffnessChanged();
|
emit stiffnessChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] qreal damping() const {
|
[[nodiscard]] qreal damping() const { return m_damping; }
|
||||||
return m_damping;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDamping(qreal d) {
|
void setDamping(qreal d) {
|
||||||
if (!qFuzzyCompare(m_damping, d)) {
|
if (!qFuzzyCompare(m_damping, d)) {
|
||||||
m_damping = d;
|
m_damping = d;
|
||||||
emit dampingChanged();
|
emit dampingChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] qreal deformScale() const {
|
[[nodiscard]] qreal deformScale() const { return m_deformScale; }
|
||||||
return m_deformScale;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDeformScale(qreal s) {
|
void setDeformScale(qreal s) {
|
||||||
if (!qFuzzyCompare(m_deformScale, s)) {
|
if (!qFuzzyCompare(m_deformScale, s)) {
|
||||||
m_deformScale = s;
|
m_deformScale = s;
|
||||||
emit deformScaleChanged();
|
emit deformScaleChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QQmlListProperty<BlobRect> exclude();
|
QQmlListProperty<BlobRect> exclude();
|
||||||
|
|
||||||
bool isExcluded(const BlobShape* other) const override;
|
bool isExcluded(const BlobShape* other) const override;
|
||||||
void cornerRadii(float out[4]) const override;
|
void cornerRadii(float out[4]) const override;
|
||||||
|
|
||||||
[[nodiscard]] qreal topLeftRadius() const {
|
[[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; }
|
||||||
return m_topLeftRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTopLeftRadius(qreal r);
|
void setTopLeftRadius(qreal r);
|
||||||
|
|
||||||
[[nodiscard]] qreal topRightRadius() const {
|
[[nodiscard]] qreal topRightRadius() const { return m_topRightRadius; }
|
||||||
return m_topRightRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setTopRightRadius(qreal r);
|
void setTopRightRadius(qreal r);
|
||||||
|
|
||||||
[[nodiscard]] qreal bottomLeftRadius() const {
|
[[nodiscard]] qreal bottomLeftRadius() const { return m_bottomLeftRadius; }
|
||||||
return m_bottomLeftRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBottomLeftRadius(qreal r);
|
void setBottomLeftRadius(qreal r);
|
||||||
|
|
||||||
[[nodiscard]] qreal bottomRightRadius() const {
|
[[nodiscard]] qreal bottomRightRadius() const {
|
||||||
return m_bottomRightRadius;
|
return m_bottomRightRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBottomRightRadius(qreal r);
|
void setBottomRightRadius(qreal r);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void stiffnessChanged();
|
void stiffnessChanged();
|
||||||
void dampingChanged();
|
void dampingChanged();
|
||||||
void deformScaleChanged();
|
void deformScaleChanged();
|
||||||
void excludeChanged();
|
void excludeChanged();
|
||||||
void topLeftRadiusChanged();
|
void topLeftRadiusChanged();
|
||||||
void topRightRadiusChanged();
|
void topRightRadiusChanged();
|
||||||
void bottomLeftRadiusChanged();
|
void bottomLeftRadiusChanged();
|
||||||
void bottomRightRadiusChanged();
|
void bottomRightRadiusChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void updatePolish() override;
|
void updatePolish() override;
|
||||||
void updatePhysics() override;
|
void updatePhysics() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void checkAtRest(float speed);
|
void checkAtRest(float speed);
|
||||||
|
|
||||||
// Physics state
|
// Physics state
|
||||||
QPointF m_prevScenePos;
|
QPointF m_prevScenePos;
|
||||||
QElapsedTimer m_elapsed;
|
QElapsedTimer m_elapsed;
|
||||||
bool m_physicsActive = false;
|
bool m_physicsActive = false;
|
||||||
bool m_hasPrevPos = false;
|
bool m_hasPrevPos = false;
|
||||||
|
|
||||||
// Symmetric 2x2 deformation matrix components (3 independent: m00, m01,
|
// Symmetric 2x2 deformation matrix components (3 independent: m00, m01,
|
||||||
// m11) Rest state is identity: m00=1, m01=0, m11=1
|
// m11) Rest state is identity: m00=1, m01=0, m11=1
|
||||||
float m_dm00 = 1.0f;
|
float m_dm00 = 1.0f;
|
||||||
float m_dm01 = 0.0f;
|
float m_dm01 = 0.0f;
|
||||||
float m_dm11 = 1.0f;
|
float m_dm11 = 1.0f;
|
||||||
|
|
||||||
// Spring velocities for each component
|
// Spring velocities for each component
|
||||||
float m_dmVel00 = 0.0f;
|
float m_dmVel00 = 0.0f;
|
||||||
float m_dmVel01 = 0.0f;
|
float m_dmVel01 = 0.0f;
|
||||||
float m_dmVel11 = 0.0f;
|
float m_dmVel11 = 0.0f;
|
||||||
|
|
||||||
qreal m_stiffness = 200.0;
|
qreal m_stiffness = 200.0;
|
||||||
qreal m_damping = 16.0;
|
qreal m_damping = 16.0;
|
||||||
qreal m_deformScale = 0.0005;
|
qreal m_deformScale = 0.0005;
|
||||||
|
|
||||||
qreal m_topLeftRadius = -1;
|
qreal m_topLeftRadius = -1;
|
||||||
qreal m_topRightRadius = -1;
|
qreal m_topRightRadius = -1;
|
||||||
qreal m_bottomLeftRadius = -1;
|
qreal m_bottomLeftRadius = -1;
|
||||||
qreal m_bottomRightRadius = -1;
|
qreal m_bottomRightRadius = -1;
|
||||||
|
|
||||||
QList<QPointer<BlobRect> > m_exclude;
|
QList<QPointer<BlobRect>> m_exclude;
|
||||||
|
|
||||||
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
static void excludeAppend(QQmlListProperty<BlobRect>* prop, BlobRect* rect);
|
||||||
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
static qsizetype excludeCount(QQmlListProperty<BlobRect>* prop);
|
||||||
static BlobRect* excludeAt(QQmlListProperty<BlobRect>* prop, qsizetype index);
|
static BlobRect* excludeAt(
|
||||||
static void excludeClear(QQmlListProperty<BlobRect>* prop);
|
QQmlListProperty<BlobRect>* prop, qsizetype index);
|
||||||
static void excludeReplace(QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
static void excludeClear(QQmlListProperty<BlobRect>* prop);
|
||||||
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
|
static void excludeReplace(
|
||||||
|
QQmlListProperty<BlobRect>* prop, qsizetype index, BlobRect* rect);
|
||||||
|
static void excludeRemoveLast(QQmlListProperty<BlobRect>* prop);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
|
|||||||
return std::max(extraX, extraY);
|
return std::max(extraX, extraY);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float cpuSdBox(float px, float py, float cx, float cy, float hw, float hh) {
|
static float cpuSdBox(
|
||||||
|
float px, float py, float cx, float cy, float hw, float hh) {
|
||||||
const float dx = std::abs(px - cx) - hw;
|
const float dx = std::abs(px - cx) - hw;
|
||||||
const float dy = std::abs(py - cy) - hh;
|
const float dy = std::abs(py - cy) - hh;
|
||||||
const float mdx = std::max(dx, 0.0f);
|
const float mdx = std::max(dx, 0.0f);
|
||||||
@@ -31,54 +32,53 @@ static float cpuSmoothstep(float edge0, float edge1, float x) {
|
|||||||
return t * t * (3.0f - 2.0f * t);
|
return t * t * (3.0f - 2.0f * t);
|
||||||
}
|
}
|
||||||
|
|
||||||
BlobShape::BlobShape(QQuickItem* parent)
|
BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) {
|
||||||
: QQuickItem(parent) {
|
|
||||||
setFlag(ItemHasContents);
|
setFlag(ItemHasContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::setGroup(BlobGroup* g) {
|
void BlobShape::setGroup(BlobGroup* g) {
|
||||||
if (m_group == g)
|
if (m_group == g) return;
|
||||||
return;
|
if (m_group && isComponentComplete()) unregisterFromGroup();
|
||||||
if (m_group && isComponentComplete())
|
|
||||||
unregisterFromGroup();
|
|
||||||
m_group = g;
|
m_group = g;
|
||||||
if (m_group && isComponentComplete())
|
if (m_group && isComponentComplete()) registerWithGroup();
|
||||||
registerWithGroup();
|
|
||||||
emit groupChanged();
|
emit groupChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::setRadius(qreal r) {
|
void BlobShape::setRadius(qreal r) {
|
||||||
if (qFuzzyCompare(m_radius, r))
|
if (qFuzzyCompare(m_radius, r)) return;
|
||||||
return;
|
|
||||||
m_radius = r;
|
m_radius = r;
|
||||||
emit radiusChanged();
|
emit radiusChanged();
|
||||||
if (m_group)
|
if (m_group) m_group->markDirty();
|
||||||
m_group->markDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::componentComplete() {
|
void BlobShape::componentComplete() {
|
||||||
QQuickItem::componentComplete();
|
QQuickItem::componentComplete();
|
||||||
if (m_group)
|
if (m_group) registerWithGroup();
|
||||||
registerWithGroup();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) {
|
void BlobShape::geometryChange(
|
||||||
|
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||||
updateCenteredDeformMatrix();
|
updateCenteredDeformMatrix();
|
||||||
if (m_group) {
|
if (m_group) {
|
||||||
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
|
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
|
||||||
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
|
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
|
||||||
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width());
|
m_pendingDw +=
|
||||||
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height());
|
static_cast<float>(newGeometry.width() - oldGeometry.width());
|
||||||
|
m_pendingDh +=
|
||||||
|
static_cast<float>(newGeometry.height() - oldGeometry.height());
|
||||||
|
|
||||||
const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) +
|
const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) +
|
||||||
std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f);
|
std::abs(m_deformMatrix(0, 1)) +
|
||||||
|
std::abs(m_deformMatrix(1, 0)) +
|
||||||
|
std::abs(m_deformMatrix(1, 1) - 1.0f);
|
||||||
const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f;
|
const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f;
|
||||||
|
|
||||||
if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold ||
|
if (std::abs(m_pendingDx) > syncThreshold ||
|
||||||
std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) {
|
std::abs(m_pendingDy) > syncThreshold ||
|
||||||
|
std::abs(m_pendingDw) > syncThreshold ||
|
||||||
|
std::abs(m_pendingDh) > syncThreshold) {
|
||||||
m_pendingDx = 0;
|
m_pendingDx = 0;
|
||||||
m_pendingDy = 0;
|
m_pendingDy = 0;
|
||||||
m_pendingDw = 0;
|
m_pendingDw = 0;
|
||||||
@@ -111,18 +111,15 @@ void BlobShape::cornerRadii(float out[4]) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::registerWithGroup() {
|
void BlobShape::registerWithGroup() {
|
||||||
if (m_group)
|
if (m_group) m_group->addShape(this);
|
||||||
m_group->addShape(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::unregisterFromGroup() {
|
void BlobShape::unregisterFromGroup() {
|
||||||
if (m_group)
|
if (m_group) m_group->removeShape(this);
|
||||||
m_group->removeShape(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlobShape::updatePolish() {
|
void BlobShape::updatePolish() {
|
||||||
if (!m_group)
|
if (!m_group) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_group->ensurePhysicsUpdated();
|
m_group->ensurePhysicsUpdated();
|
||||||
|
|
||||||
@@ -144,27 +141,30 @@ void BlobShape::updatePolish() {
|
|||||||
m_cachedPaddedY = static_cast<float>(scenePos.y()) - totalPad;
|
m_cachedPaddedY = static_cast<float>(scenePos.y()) - totalPad;
|
||||||
m_cachedPaddedW = static_cast<float>(width()) + 2.0f * totalPad;
|
m_cachedPaddedW = static_cast<float>(width()) + 2.0f * totalPad;
|
||||||
m_cachedPaddedH = static_cast<float>(height()) + 2.0f * totalPad;
|
m_cachedPaddedH = static_cast<float>(height()) + 2.0f * totalPad;
|
||||||
m_localPaddedRect = QRectF(static_cast<double>(-totalPad), static_cast<double>(-totalPad),
|
m_localPaddedRect = QRectF(
|
||||||
width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad));
|
static_cast<double>(-totalPad),
|
||||||
|
static_cast<double>(-totalPad),
|
||||||
|
width() + 2.0 * static_cast<double>(totalPad),
|
||||||
|
height() + 2.0 * static_cast<double>(totalPad));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cachedRects.clear();
|
m_cachedRects.clear();
|
||||||
m_cachedMyIndex = -2;
|
m_cachedMyIndex = -2;
|
||||||
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
|
const QRectF myPadded(
|
||||||
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH));
|
static_cast<double>(m_cachedPaddedX),
|
||||||
|
static_cast<double>(m_cachedPaddedY),
|
||||||
|
static_cast<double>(m_cachedPaddedW),
|
||||||
|
static_cast<double>(m_cachedPaddedH));
|
||||||
|
|
||||||
QVector<BlobShape*> rectShapes;
|
QVector<BlobShape*> rectShapes;
|
||||||
rectShapes.reserve(m_group->shapes().size());
|
rectShapes.reserve(m_group->shapes().size());
|
||||||
|
|
||||||
for (BlobShape* other : m_group->shapes()) {
|
for (BlobShape* other : m_group->shapes()) {
|
||||||
if (other->isInvertedRect())
|
if (other->isInvertedRect()) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (other->width() <= 0 || other->height() <= 0)
|
if (other->width() <= 0 || other->height() <= 0) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (isExcluded(other))
|
if (isExcluded(other)) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
const QPointF otherScene = other->mapToScene(QPointF(0, 0));
|
const QPointF otherScene = other->mapToScene(QPointF(0, 0));
|
||||||
|
|
||||||
@@ -174,10 +174,13 @@ void BlobShape::updatePolish() {
|
|||||||
} else {
|
} else {
|
||||||
const float otherHW = static_cast<float>(other->width()) * 0.5f;
|
const float otherHW = static_cast<float>(other->width()) * 0.5f;
|
||||||
const float otherHH = static_cast<float>(other->height()) * 0.5f;
|
const float otherHH = static_cast<float>(other->height()) * 0.5f;
|
||||||
const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH);
|
const float otherPad =
|
||||||
const QRectF otherPadded(otherScene.x() - static_cast<double>(otherPad),
|
pad + deformPadding(other->m_deformMatrix, otherHW, otherHH);
|
||||||
otherScene.y() - static_cast<double>(otherPad), other->width() + 2.0 * static_cast<double>(otherPad),
|
const QRectF otherPadded(
|
||||||
other->height() + 2.0 * static_cast<double>(otherPad));
|
otherScene.x() - static_cast<double>(otherPad),
|
||||||
|
otherScene.y() - static_cast<double>(otherPad),
|
||||||
|
other->width() + 2.0 * static_cast<double>(otherPad),
|
||||||
|
other->height() + 2.0 * static_cast<double>(otherPad));
|
||||||
include = myPadded.intersects(otherPadded);
|
include = myPadded.intersects(otherPadded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,19 +220,16 @@ void BlobShape::updatePolish() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInvertedRect())
|
if (isInvertedRect()) m_cachedMyIndex = -1;
|
||||||
m_cachedMyIndex = -1;
|
|
||||||
|
|
||||||
const auto cachedCount = m_cachedRects.size();
|
const auto cachedCount = m_cachedRects.size();
|
||||||
for (qsizetype i = 0; i < cachedCount; ++i) {
|
for (qsizetype i = 0; i < cachedCount; ++i) {
|
||||||
int mask = 0;
|
int mask = 0;
|
||||||
BlobShape* si = rectShapes[i];
|
BlobShape* si = rectShapes[i];
|
||||||
for (qsizetype j = 0; j < cachedCount; ++j) {
|
for (qsizetype j = 0; j < cachedCount; ++j) {
|
||||||
if (j == i)
|
if (j == i) continue;
|
||||||
continue;
|
|
||||||
BlobShape* sj = rectShapes[j];
|
BlobShape* sj = rectShapes[j];
|
||||||
if (si->isExcluded(sj) || sj->isExcluded(si))
|
if (si->isExcluded(sj) || sj->isExcluded(si)) mask |= (1 << j);
|
||||||
mask |= (1 << j);
|
|
||||||
}
|
}
|
||||||
m_cachedRects[i].excludeMask = mask;
|
m_cachedRects[i].excludeMask = mask;
|
||||||
}
|
}
|
||||||
@@ -242,15 +242,25 @@ void BlobShape::updatePolish() {
|
|||||||
auto* inv = m_group->invertedRect();
|
auto* inv = m_group->invertedRect();
|
||||||
if (inv) {
|
if (inv) {
|
||||||
const QPointF invScene = inv->mapToScene(QPointF(0, 0));
|
const QPointF invScene = inv->mapToScene(QPointF(0, 0));
|
||||||
const float outerCX = static_cast<float>(invScene.x() + inv->width() / 2.0);
|
const float outerCX =
|
||||||
const float outerCY = static_cast<float>(invScene.y() + inv->height() / 2.0);
|
static_cast<float>(invScene.x() + inv->width() / 2.0);
|
||||||
|
const float outerCY =
|
||||||
|
static_cast<float>(invScene.y() + inv->height() / 2.0);
|
||||||
const float outerHW = static_cast<float>(inv->width() / 2.0);
|
const float outerHW = static_cast<float>(inv->width() / 2.0);
|
||||||
const float outerHH = static_cast<float>(inv->height() / 2.0);
|
const float outerHH = static_cast<float>(inv->height() / 2.0);
|
||||||
|
|
||||||
const float innerCX = outerCX + static_cast<float>((inv->borderLeft() - inv->borderRight()) / 2.0);
|
const float innerCX =
|
||||||
const float innerCY = outerCY + static_cast<float>((inv->borderTop() - inv->borderBottom()) / 2.0);
|
outerCX +
|
||||||
const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
|
static_cast<float>((inv->borderLeft() - inv->borderRight()) / 2.0);
|
||||||
const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
|
const float innerCY =
|
||||||
|
outerCY +
|
||||||
|
static_cast<float>((inv->borderTop() - inv->borderBottom()) / 2.0);
|
||||||
|
const float innerHW =
|
||||||
|
outerHW -
|
||||||
|
static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
|
||||||
|
const float innerHH =
|
||||||
|
outerHH -
|
||||||
|
static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
|
||||||
|
|
||||||
bool nearBorder = isInvertedRect();
|
bool nearBorder = isInvertedRect();
|
||||||
if (!nearBorder) {
|
if (!nearBorder) {
|
||||||
@@ -259,8 +269,10 @@ void BlobShape::updatePolish() {
|
|||||||
const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f;
|
const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f;
|
||||||
const float myHW = m_cachedPaddedW * 0.5f;
|
const float myHW = m_cachedPaddedW * 0.5f;
|
||||||
const float myHH = m_cachedPaddedH * 0.5f;
|
const float myHH = m_cachedPaddedH * 0.5f;
|
||||||
nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) ||
|
nearBorder = (myCX - myHW < innerCX - innerHW + margin) ||
|
||||||
(myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin);
|
(myCX + myHW > innerCX + innerHW - margin) ||
|
||||||
|
(myCY - myHH < innerCY - innerHH + margin) ||
|
||||||
|
(myCY + myHH > innerCY + innerHH - margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nearBorder) {
|
if (nearBorder) {
|
||||||
@@ -293,15 +305,33 @@ void BlobShape::updatePolish() {
|
|||||||
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh;
|
||||||
|
|
||||||
for (qsizetype j = 0; j < rectCount; ++j) {
|
for (qsizetype j = 0; j < rectCount; ++j) {
|
||||||
if (j == i)
|
if (j == i) continue;
|
||||||
continue;
|
if (riExcludeMask & (1 << j)) continue;
|
||||||
if (riExcludeMask & (1 << j))
|
|
||||||
continue;
|
|
||||||
const auto& rj = m_cachedRects[j];
|
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)));
|
fTr = std::min(
|
||||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
fTr,
|
||||||
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
cpuSmoothstep(
|
||||||
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_cachedHasInverted) {
|
if (m_cachedHasInverted) {
|
||||||
@@ -309,10 +339,30 @@ void BlobShape::updatePolish() {
|
|||||||
const float icy = m_cachedInvertedInner[1];
|
const float icy = m_cachedInvertedInner[1];
|
||||||
const float ihw = m_cachedInvertedInner[2];
|
const float ihw = m_cachedInvertedInner[2];
|
||||||
const float ihh = m_cachedInvertedInner[3];
|
const float ihh = m_cachedInvertedInner[3];
|
||||||
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh)));
|
fTr = std::min(
|
||||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh)));
|
fTr,
|
||||||
fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh)));
|
cpuSmoothstep(
|
||||||
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
|
0.0f,
|
||||||
|
smoothFactor,
|
||||||
|
-cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh)));
|
||||||
|
fBr = std::min(
|
||||||
|
fBr,
|
||||||
|
cpuSmoothstep(
|
||||||
|
0.0f,
|
||||||
|
smoothFactor,
|
||||||
|
-cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh)));
|
||||||
|
fBl = std::min(
|
||||||
|
fBl,
|
||||||
|
cpuSmoothstep(
|
||||||
|
0.0f,
|
||||||
|
smoothFactor,
|
||||||
|
-cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh)));
|
||||||
|
fTl = std::min(
|
||||||
|
fTl,
|
||||||
|
cpuSmoothstep(
|
||||||
|
0.0f,
|
||||||
|
smoothFactor,
|
||||||
|
-cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
|
ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
|
||||||
@@ -332,7 +382,8 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
|||||||
if (!node) {
|
if (!node) {
|
||||||
node = new QSGGeometryNode;
|
node = new QSGGeometryNode;
|
||||||
|
|
||||||
auto* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
|
auto* geometry =
|
||||||
|
new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
|
||||||
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip);
|
||||||
node->setGeometry(geometry);
|
node->setGeometry(geometry);
|
||||||
node->setFlag(QSGNode::OwnsGeometry);
|
node->setFlag(QSGNode::OwnsGeometry);
|
||||||
@@ -368,10 +419,17 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
|
|||||||
material->m_color = m_group->color();
|
material->m_color = m_group->color();
|
||||||
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
|
material->m_hasInverted = m_cachedHasInverted ? 1 : 0;
|
||||||
material->m_invertedRadius = m_cachedInvertedRadius;
|
material->m_invertedRadius = m_cachedInvertedRadius;
|
||||||
memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter));
|
memcpy(
|
||||||
memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner));
|
material->m_invertedOuter,
|
||||||
|
m_cachedInvertedOuter,
|
||||||
|
sizeof(m_cachedInvertedOuter));
|
||||||
|
memcpy(
|
||||||
|
material->m_invertedInner,
|
||||||
|
m_cachedInvertedInner,
|
||||||
|
sizeof(m_cachedInvertedInner));
|
||||||
|
|
||||||
const int count = static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
const int count =
|
||||||
|
static_cast<int>(qMin(m_cachedRects.size(), qsizetype(16)));
|
||||||
material->m_rectCount = count;
|
material->m_rectCount = count;
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < count; ++i)
|
||||||
material->m_rects[i] = m_cachedRects[i];
|
material->m_rects[i] = m_cachedRects[i];
|
||||||
|
|||||||
@@ -9,85 +9,78 @@
|
|||||||
class BlobGroup;
|
class BlobGroup;
|
||||||
|
|
||||||
class BlobShape : public QQuickItem {
|
class BlobShape : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged)
|
Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged)
|
||||||
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
|
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
|
||||||
Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY rawDeformMatrixChanged)
|
QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY
|
||||||
|
rawDeformMatrixChanged)
|
||||||
|
|
||||||
friend class BlobGroup;
|
friend class BlobGroup;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BlobShape(QQuickItem* parent = nullptr);
|
explicit BlobShape(QQuickItem* parent = nullptr);
|
||||||
~BlobShape() override = default;
|
~BlobShape() override = default;
|
||||||
|
|
||||||
[[nodiscard]] BlobGroup* group() const {
|
[[nodiscard]] BlobGroup* group() const { return m_group; }
|
||||||
return m_group;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setGroup(BlobGroup* g);
|
void setGroup(BlobGroup* g);
|
||||||
|
|
||||||
[[nodiscard]] qreal radius() const {
|
[[nodiscard]] qreal radius() const { return m_radius; }
|
||||||
return m_radius;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setRadius(qreal r);
|
void setRadius(qreal r);
|
||||||
|
|
||||||
[[nodiscard]] QMatrix4x4 deformMatrix() const {
|
[[nodiscard]] QMatrix4x4 deformMatrix() const {
|
||||||
return m_centeredDeformMatrix;
|
return m_centeredDeformMatrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QMatrix4x4 rawDeformMatrix() const {
|
[[nodiscard]] QMatrix4x4 rawDeformMatrix() const { return m_deformMatrix; }
|
||||||
return m_deformMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void groupChanged();
|
void groupChanged();
|
||||||
void radiusChanged();
|
void radiusChanged();
|
||||||
void deformMatrixChanged();
|
void deformMatrixChanged();
|
||||||
void rawDeformMatrixChanged();
|
void rawDeformMatrixChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void componentComplete() override;
|
void componentComplete() override;
|
||||||
void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
void geometryChange(
|
||||||
void updatePolish() override;
|
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||||
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
|
void updatePolish() override;
|
||||||
|
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool isInvertedRect() const {
|
[[nodiscard]] virtual bool isInvertedRect() const { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool isExcluded(const BlobShape* /*other*/) const {
|
virtual bool isExcluded(const BlobShape*) const { return false; }
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void cornerRadii(float out[4]) const;
|
virtual void cornerRadii(float out[4]) const;
|
||||||
|
|
||||||
virtual void updatePhysics() {
|
virtual void updatePhysics() {}
|
||||||
}
|
|
||||||
|
|
||||||
virtual void registerWithGroup();
|
virtual void registerWithGroup();
|
||||||
virtual void unregisterFromGroup();
|
virtual void unregisterFromGroup();
|
||||||
void updateCenteredDeformMatrix();
|
void updateCenteredDeformMatrix();
|
||||||
|
|
||||||
BlobGroup* m_group = nullptr;
|
BlobGroup* m_group = nullptr;
|
||||||
qreal m_radius = 0;
|
qreal m_radius = 0;
|
||||||
QMatrix4x4 m_deformMatrix;
|
QMatrix4x4 m_deformMatrix;
|
||||||
QMatrix4x4 m_centeredDeformMatrix;
|
QMatrix4x4 m_centeredDeformMatrix;
|
||||||
|
|
||||||
float m_cachedPaddedX = 0;
|
float m_cachedPaddedX = 0;
|
||||||
float m_cachedPaddedY = 0;
|
float m_cachedPaddedY = 0;
|
||||||
float m_cachedPaddedW = 0;
|
float m_cachedPaddedW = 0;
|
||||||
float m_cachedPaddedH = 0;
|
float m_cachedPaddedH = 0;
|
||||||
QRectF m_localPaddedRect;
|
QRectF m_localPaddedRect;
|
||||||
QVector<BlobRectData> m_cachedRects;
|
QVector<BlobRectData> m_cachedRects;
|
||||||
int m_cachedMyIndex = -2;
|
int m_cachedMyIndex = -2;
|
||||||
float m_pendingDx = 0;
|
float m_pendingDx = 0;
|
||||||
float m_pendingDy = 0;
|
float m_pendingDy = 0;
|
||||||
float m_pendingDw = 0;
|
float m_pendingDw = 0;
|
||||||
float m_pendingDh = 0;
|
float m_pendingDh = 0;
|
||||||
bool m_cachedHasInverted = false;
|
bool m_cachedHasInverted = false;
|
||||||
float m_cachedInvertedRadius = 0;
|
float m_cachedInvertedRadius = 0;
|
||||||
float m_cachedInvertedOuter[4] = {};
|
float m_cachedInvertedOuter[4] = {};
|
||||||
float m_cachedInvertedInner[4] = {};
|
float m_cachedInvertedInner[4] = {};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,10 @@
|
|||||||
namespace ZShell::components {
|
namespace ZShell::components {
|
||||||
|
|
||||||
ButtonRow::ButtonRow(QQuickItem* parent)
|
ButtonRow::ButtonRow(QQuickItem* parent)
|
||||||
: QQuickItem(parent)
|
: QQuickItem(parent), m_dirty(false), m_spacing(0.0) {
|
||||||
, m_dirty(false)
|
|
||||||
, m_spacing(0.0) {
|
|
||||||
setFlag(QQuickItem::ItemHasContents, true);
|
setFlag(QQuickItem::ItemHasContents, true);
|
||||||
QObject::connect(this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate);
|
QObject::connect(
|
||||||
|
this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal ButtonRow::spacing() const {
|
qreal ButtonRow::spacing() const {
|
||||||
@@ -15,8 +14,7 @@ qreal ButtonRow::spacing() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ButtonRow::setSpacing(qreal spacing) {
|
void ButtonRow::setSpacing(qreal spacing) {
|
||||||
if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0))
|
if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0)) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_spacing = spacing;
|
m_spacing = spacing;
|
||||||
emit spacingChanged();
|
emit spacingChanged();
|
||||||
@@ -24,18 +22,33 @@ void ButtonRow::setSpacing(qreal spacing) {
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ButtonRow::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) {
|
void ButtonRow::itemChange(
|
||||||
|
QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) {
|
||||||
if (change == QQuickItem::ItemChildAddedChange) {
|
if (change == QQuickItem::ItemChildAddedChange) {
|
||||||
auto* const child = data.item;
|
auto* const child = data.item;
|
||||||
QObject::connect(child, &QQuickItem::implicitWidthChanged, this, &ButtonRow::invalidate);
|
QObject::connect(
|
||||||
QObject::connect(child, &QQuickItem::implicitHeightChanged, this, &ButtonRow::invalidate);
|
child,
|
||||||
QObject::connect(child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate);
|
&QQuickItem::implicitWidthChanged,
|
||||||
|
this,
|
||||||
|
&ButtonRow::invalidate);
|
||||||
|
QObject::connect(
|
||||||
|
child,
|
||||||
|
&QQuickItem::implicitHeightChanged,
|
||||||
|
this,
|
||||||
|
&ButtonRow::invalidate);
|
||||||
|
QObject::connect(
|
||||||
|
child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate);
|
||||||
|
|
||||||
const auto* childMeta = child->metaObject();
|
const auto* childMeta = child->metaObject();
|
||||||
const auto morphSignalIdx = childMeta->indexOfSignal("shapeMorphExpansionChanged()");
|
const auto morphSignalIdx =
|
||||||
|
childMeta->indexOfSignal("shapeMorphExpansionChanged()");
|
||||||
if (morphSignalIdx != -1)
|
if (morphSignalIdx != -1)
|
||||||
QObject::connect(child, childMeta->method(morphSignalIdx), this,
|
QObject::connect(
|
||||||
metaObject()->method(metaObject()->indexOfSlot("invalidate()")));
|
child,
|
||||||
|
childMeta->method(morphSignalIdx),
|
||||||
|
this,
|
||||||
|
metaObject()->method(
|
||||||
|
metaObject()->indexOfSlot("invalidate()")));
|
||||||
|
|
||||||
invalidate();
|
invalidate();
|
||||||
} else if (change == QQuickItem::ItemChildRemovedChange) {
|
} else if (change == QQuickItem::ItemChildRemovedChange) {
|
||||||
@@ -77,8 +90,7 @@ void ButtonRow::relayout() {
|
|||||||
maxHeight = qMax(maxHeight, child->implicitHeight());
|
maxHeight = qMax(maxHeight, child->implicitHeight());
|
||||||
|
|
||||||
const auto prop = child->property("fillWidth");
|
const auto prop = child->property("fillWidth");
|
||||||
if (!prop.isValid())
|
if (!prop.isValid()) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (prop.toBool()) {
|
if (prop.toBool()) {
|
||||||
fillWidthCount++;
|
fillWidthCount++;
|
||||||
@@ -88,15 +100,17 @@ void ButtonRow::relayout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fillWidthCount == 0)
|
if (fillWidthCount == 0) fillWidthCount = 1; // Avoid divide by 0
|
||||||
fillWidthCount = 1; // Avoid divide by 0
|
|
||||||
|
|
||||||
const auto widthPerItem = (width() - totalSpacing - reservedWidth) / static_cast<qreal>(fillWidthCount);
|
const auto widthPerItem = (width() - totalSpacing - reservedWidth) /
|
||||||
|
static_cast<qreal>(fillWidthCount);
|
||||||
|
|
||||||
QList<qreal> baseWidths;
|
QList<qreal> baseWidths;
|
||||||
baseWidths.reserve(nChildren);
|
baseWidths.reserve(nChildren);
|
||||||
for (auto* const child : validChildren)
|
for (auto* const child : validChildren)
|
||||||
baseWidths.append(child->property("fillWidth").toBool() ? widthPerItem : child->implicitWidth());
|
baseWidths.append(
|
||||||
|
child->property("fillWidth").toBool() ? widthPerItem
|
||||||
|
: child->implicitWidth());
|
||||||
|
|
||||||
qreal accX = 0;
|
qreal accX = 0;
|
||||||
for (int i = 0; i < nChildren; ++i) {
|
for (int i = 0; i < nChildren; ++i) {
|
||||||
@@ -108,12 +122,12 @@ void ButtonRow::relayout() {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// Items at edges push by full amount, items in middle push by half
|
// Items at edges push by full amount, items in middle push by half
|
||||||
if (i > 1)
|
if (i > 1) prevExtraWidth /= 2;
|
||||||
prevExtraWidth /= 2;
|
if (i < nChildren - 2) nextExtraWidth /= 2;
|
||||||
if (i < nChildren - 2)
|
|
||||||
nextExtraWidth /= 2;
|
|
||||||
|
|
||||||
child->setWidth(baseWidths[i] + getMorphExpansion(child) - prevExtraWidth - nextExtraWidth);
|
child->setWidth(
|
||||||
|
baseWidths[i] + getMorphExpansion(child) - prevExtraWidth -
|
||||||
|
nextExtraWidth);
|
||||||
child->setHeight(maxHeight);
|
child->setHeight(maxHeight);
|
||||||
|
|
||||||
child->setX(accX);
|
child->setX(accX);
|
||||||
|
|||||||
@@ -5,33 +5,34 @@
|
|||||||
namespace ZShell::components {
|
namespace ZShell::components {
|
||||||
|
|
||||||
class ButtonRow : public QQuickItem {
|
class ButtonRow : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ButtonRow(QQuickItem* parent = nullptr);
|
explicit ButtonRow(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] qreal spacing() const;
|
[[nodiscard]] qreal spacing() const;
|
||||||
void setSpacing(qreal spacing);
|
void setSpacing(qreal spacing);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void spacingChanged();
|
void spacingChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) override;
|
void itemChange(
|
||||||
void updatePolish() override;
|
QQuickItem::ItemChange change,
|
||||||
|
const QQuickItem::ItemChangeData& data) override;
|
||||||
|
void updatePolish() override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void invalidate();
|
void invalidate();
|
||||||
|
|
||||||
private:
|
void relayout();
|
||||||
void relayout();
|
static qreal getMorphExpansion(const QQuickItem* item);
|
||||||
static qreal getMorphExpansion(const QQuickItem* item);
|
|
||||||
|
|
||||||
bool m_dirty;
|
bool m_dirty;
|
||||||
qreal m_spacing;
|
qreal m_spacing;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::components
|
} // namespace ZShell::components
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ qreal LazyListViewAttached::preferredHeight() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListViewAttached::setPreferredHeight(qreal height) {
|
void LazyListViewAttached::setPreferredHeight(qreal height) {
|
||||||
if (qFuzzyCompare(m_preferredHeight + 1.0, height + 1.0))
|
if (qFuzzyCompare(m_preferredHeight + 1.0, height + 1.0)) return;
|
||||||
return;
|
|
||||||
m_preferredHeight = height;
|
m_preferredHeight = height;
|
||||||
emit preferredHeightChanged();
|
emit preferredHeightChanged();
|
||||||
}
|
}
|
||||||
@@ -32,8 +31,7 @@ qreal LazyListViewAttached::visibleHeight() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListViewAttached::setVisibleHeight(qreal height) {
|
void LazyListViewAttached::setVisibleHeight(qreal height) {
|
||||||
if (qFuzzyCompare(m_visibleHeight + 1.0, height + 1.0))
|
if (qFuzzyCompare(m_visibleHeight + 1.0, height + 1.0)) return;
|
||||||
return;
|
|
||||||
m_visibleHeight = height;
|
m_visibleHeight = height;
|
||||||
emit visibleHeightChanged();
|
emit visibleHeightChanged();
|
||||||
}
|
}
|
||||||
@@ -43,8 +41,7 @@ bool LazyListViewAttached::ready() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListViewAttached::setReady(bool ready) {
|
void LazyListViewAttached::setReady(bool ready) {
|
||||||
if (m_ready == ready)
|
if (m_ready == ready) return;
|
||||||
return;
|
|
||||||
m_ready = ready;
|
m_ready = ready;
|
||||||
emit readyChanged();
|
emit readyChanged();
|
||||||
}
|
}
|
||||||
@@ -54,8 +51,7 @@ bool LazyListViewAttached::adding() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListViewAttached::setAdding(bool adding) {
|
void LazyListViewAttached::setAdding(bool adding) {
|
||||||
if (m_adding == adding)
|
if (m_adding == adding) return;
|
||||||
return;
|
|
||||||
m_adding = adding;
|
m_adding = adding;
|
||||||
emit addingChanged();
|
emit addingChanged();
|
||||||
}
|
}
|
||||||
@@ -65,8 +61,7 @@ bool LazyListViewAttached::removing() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListViewAttached::setRemoving(bool removing) {
|
void LazyListViewAttached::setRemoving(bool removing) {
|
||||||
if (m_removing == removing)
|
if (m_removing == removing) return;
|
||||||
return;
|
|
||||||
m_removing = removing;
|
m_removing = removing;
|
||||||
emit removingChanged();
|
emit removingChanged();
|
||||||
}
|
}
|
||||||
@@ -76,8 +71,7 @@ bool LazyListViewAttached::trackViewport() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListViewAttached::setTrackViewport(bool track) {
|
void LazyListViewAttached::setTrackViewport(bool track) {
|
||||||
if (m_trackViewport == track)
|
if (m_trackViewport == track) return;
|
||||||
return;
|
|
||||||
m_trackViewport = track;
|
m_trackViewport = track;
|
||||||
emit trackViewportChanged();
|
emit trackViewportChanged();
|
||||||
}
|
}
|
||||||
@@ -102,16 +96,13 @@ QAbstractItemModel* LazyListView::model() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setModel(QAbstractItemModel* model) {
|
void LazyListView::setModel(QAbstractItemModel* model) {
|
||||||
if (m_model == model)
|
if (m_model == model) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_model)
|
if (m_model) disconnectModel();
|
||||||
disconnectModel();
|
|
||||||
|
|
||||||
m_model = model;
|
m_model = model;
|
||||||
|
|
||||||
if (m_model)
|
if (m_model) connectModel();
|
||||||
connectModel();
|
|
||||||
|
|
||||||
resetContent();
|
resetContent();
|
||||||
emit modelChanged();
|
emit modelChanged();
|
||||||
@@ -122,8 +113,7 @@ QQmlComponent* LazyListView::delegate() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setDelegate(QQmlComponent* delegate) {
|
void LazyListView::setDelegate(QQmlComponent* delegate) {
|
||||||
if (m_delegate == delegate)
|
if (m_delegate == delegate) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_delegate = delegate;
|
m_delegate = delegate;
|
||||||
resetContent();
|
resetContent();
|
||||||
@@ -135,8 +125,7 @@ qreal LazyListView::spacing() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setSpacing(qreal spacing) {
|
void LazyListView::setSpacing(qreal spacing) {
|
||||||
if (qFuzzyCompare(m_spacing, spacing))
|
if (qFuzzyCompare(m_spacing, spacing)) return;
|
||||||
return;
|
|
||||||
m_spacing = spacing;
|
m_spacing = spacing;
|
||||||
emit spacingChanged();
|
emit spacingChanged();
|
||||||
polish();
|
polish();
|
||||||
@@ -155,8 +144,7 @@ qreal LazyListView::contentY() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setContentY(qreal contentY) {
|
void LazyListView::setContentY(qreal contentY) {
|
||||||
if (qFuzzyCompare(m_contentY, contentY))
|
if (qFuzzyCompare(m_contentY, contentY)) return;
|
||||||
return;
|
|
||||||
m_contentY = contentY;
|
m_contentY = contentY;
|
||||||
emit contentYChanged();
|
emit contentYChanged();
|
||||||
polish();
|
polish();
|
||||||
@@ -167,12 +155,10 @@ QRectF LazyListView::viewport() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setViewport(const QRectF& viewport) {
|
void LazyListView::setViewport(const QRectF& viewport) {
|
||||||
if (m_viewport == viewport)
|
if (m_viewport == viewport) return;
|
||||||
return;
|
|
||||||
m_viewport = viewport;
|
m_viewport = viewport;
|
||||||
emit viewportChanged();
|
emit viewportChanged();
|
||||||
if (m_useCustomViewport)
|
if (m_useCustomViewport) polish();
|
||||||
polish();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LazyListView::useCustomViewport() const {
|
bool LazyListView::useCustomViewport() const {
|
||||||
@@ -180,8 +166,7 @@ bool LazyListView::useCustomViewport() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setUseCustomViewport(bool use) {
|
void LazyListView::setUseCustomViewport(bool use) {
|
||||||
if (m_useCustomViewport == use)
|
if (m_useCustomViewport == use) return;
|
||||||
return;
|
|
||||||
m_useCustomViewport = use;
|
m_useCustomViewport = use;
|
||||||
emit useCustomViewportChanged();
|
emit useCustomViewportChanged();
|
||||||
polish();
|
polish();
|
||||||
@@ -192,8 +177,7 @@ qreal LazyListView::cacheBuffer() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setCacheBuffer(qreal buffer) {
|
void LazyListView::setCacheBuffer(qreal buffer) {
|
||||||
if (qFuzzyCompare(m_cacheBuffer, buffer))
|
if (qFuzzyCompare(m_cacheBuffer, buffer)) return;
|
||||||
return;
|
|
||||||
m_cacheBuffer = buffer;
|
m_cacheBuffer = buffer;
|
||||||
emit cacheBufferChanged();
|
emit cacheBufferChanged();
|
||||||
polish();
|
polish();
|
||||||
@@ -204,8 +188,7 @@ qreal LazyListView::estimatedHeight() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setEstimatedHeight(qreal height) {
|
void LazyListView::setEstimatedHeight(qreal height) {
|
||||||
if (qFuzzyCompare(m_estimatedHeight, height))
|
if (qFuzzyCompare(m_estimatedHeight, height)) return;
|
||||||
return;
|
|
||||||
m_estimatedHeight = height;
|
m_estimatedHeight = height;
|
||||||
emit estimatedHeightChanged();
|
emit estimatedHeightChanged();
|
||||||
polish();
|
polish();
|
||||||
@@ -216,17 +199,14 @@ bool LazyListView::asynchronous() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setAsynchronous(bool async) {
|
void LazyListView::setAsynchronous(bool async) {
|
||||||
if (m_asynchronous == async)
|
if (m_asynchronous == async) return;
|
||||||
return;
|
|
||||||
m_asynchronous = async;
|
m_asynchronous = async;
|
||||||
emit asynchronousChanged();
|
emit asynchronousChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal LazyListView::effectiveEstimatedHeight() const {
|
qreal LazyListView::effectiveEstimatedHeight() const {
|
||||||
if (m_estimatedHeight >= 0)
|
if (m_estimatedHeight >= 0) return m_estimatedHeight;
|
||||||
return m_estimatedHeight;
|
if (m_knownHeightCount > 0) return m_knownHeightSum / m_knownHeightCount;
|
||||||
if (m_knownHeightCount > 0)
|
|
||||||
return m_knownHeightSum / m_knownHeightCount;
|
|
||||||
return 40;
|
return 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +221,7 @@ void LazyListView::untrackHeight(qreal height) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qreal LazyListView::delegateHeight(QQuickItem* item) {
|
qreal LazyListView::delegateHeight(QQuickItem* item) {
|
||||||
if (!item)
|
if (!item) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
@@ -253,14 +232,12 @@ qreal LazyListView::delegateHeight(QQuickItem* item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
|
qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
|
||||||
if (!item)
|
if (!item) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
if (attached) {
|
if (attached) {
|
||||||
if (attached->visibleHeight() >= 0)
|
if (attached->visibleHeight() >= 0) return attached->visibleHeight();
|
||||||
return attached->visibleHeight();
|
|
||||||
if (attached->preferredHeight() >= 0)
|
if (attached->preferredHeight() >= 0)
|
||||||
return attached->preferredHeight();
|
return attached->preferredHeight();
|
||||||
}
|
}
|
||||||
@@ -269,8 +246,7 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool LazyListView::isDelegateReady(QQuickItem* item) {
|
bool LazyListView::isDelegateReady(QQuickItem* item) {
|
||||||
if (!item)
|
if (!item) return false;
|
||||||
return false;
|
|
||||||
auto* att = qobject_cast<LazyListViewAttached*>(
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
return !att || att->ready();
|
return !att || att->ready();
|
||||||
@@ -281,8 +257,7 @@ int LazyListView::removeDuration() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setRemoveDuration(int duration) {
|
void LazyListView::setRemoveDuration(int duration) {
|
||||||
if (m_removeDuration == duration)
|
if (m_removeDuration == duration) return;
|
||||||
return;
|
|
||||||
m_removeDuration = duration;
|
m_removeDuration = duration;
|
||||||
emit removeDurationChanged();
|
emit removeDurationChanged();
|
||||||
}
|
}
|
||||||
@@ -292,8 +267,7 @@ int LazyListView::readyDelay() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::setReadyDelay(int delay) {
|
void LazyListView::setReadyDelay(int delay) {
|
||||||
if (m_readyDelay == delay)
|
if (m_readyDelay == delay) return;
|
||||||
return;
|
|
||||||
m_readyDelay = delay;
|
m_readyDelay = delay;
|
||||||
emit readyDelayChanged();
|
emit readyDelayChanged();
|
||||||
}
|
}
|
||||||
@@ -308,17 +282,15 @@ void LazyListView::componentComplete() {
|
|||||||
resetContent();
|
resetContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::geometryChange(const QRectF& newGeometry,
|
void LazyListView::geometryChange(
|
||||||
const QRectF& oldGeometry) {
|
const QRectF& newGeometry, const QRectF& oldGeometry) {
|
||||||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||||
|
|
||||||
if (!m_componentComplete)
|
if (!m_componentComplete) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (!qFuzzyCompare(newGeometry.width(), oldGeometry.width())) {
|
if (!qFuzzyCompare(newGeometry.width(), oldGeometry.width())) {
|
||||||
for (auto& entry : m_delegates) {
|
for (auto& entry : m_delegates) {
|
||||||
if (entry.item)
|
if (entry.item) entry.item->setWidth(newGeometry.width());
|
||||||
entry.item->setWidth(newGeometry.width());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,8 +298,7 @@ void LazyListView::geometryChange(const QRectF& newGeometry,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::updatePolish() {
|
void LazyListView::updatePolish() {
|
||||||
if (!m_componentComplete || !m_model || !m_delegate)
|
if (!m_componentComplete || !m_model || !m_delegate) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// Flush pending inserts — make items visible and clear the adding flag
|
// Flush pending inserts — make items visible and clear the adding flag
|
||||||
// so enter animations begin. When readyDelay > 0 the entire insert is
|
// so enter animations begin. When readyDelay > 0 the entire insert is
|
||||||
@@ -335,9 +306,9 @@ void LazyListView::updatePolish() {
|
|||||||
|
|
||||||
// Collect pending indices first — avoids scanning the entire hash each frame
|
// Collect pending indices first — avoids scanning the entire hash each frame
|
||||||
QList<int> pendingIndices;
|
QList<int> pendingIndices;
|
||||||
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) {
|
for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd();
|
||||||
if (it->pendingInsert && it->item)
|
++it) {
|
||||||
pendingIndices.append(it.key());
|
if (it->pendingInsert && it->item) pendingIndices.append(it.key());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int pendingIdx : pendingIndices) {
|
for (int pendingIdx : pendingIndices) {
|
||||||
@@ -349,8 +320,7 @@ void LazyListView::updatePolish() {
|
|||||||
auto* item = entry.item;
|
auto* item = entry.item;
|
||||||
QTimer::singleShot(m_readyDelay, this, [this, item] {
|
QTimer::singleShot(m_readyDelay, this, [this, item] {
|
||||||
auto indexIt = m_itemToIndex.find(item);
|
auto indexIt = m_itemToIndex.find(item);
|
||||||
if (indexIt == m_itemToIndex.end())
|
if (indexIt == m_itemToIndex.end()) return;
|
||||||
return;
|
|
||||||
const int idx = indexIt.value();
|
const int idx = indexIt.value();
|
||||||
auto it = m_delegates.find(idx);
|
auto it = m_delegates.find(idx);
|
||||||
if (it == m_delegates.end() || it->item != item ||
|
if (it == m_delegates.end() || it->item != item ||
|
||||||
@@ -375,14 +345,11 @@ void LazyListView::updatePolish() {
|
|||||||
? m_layout[i].height
|
? m_layout[i].height
|
||||||
: effectiveEstimatedHeight();
|
: effectiveEstimatedHeight();
|
||||||
if (h > 0) {
|
if (h > 0) {
|
||||||
if (hasVisItem)
|
if (hasVisItem) visualY += m_spacing;
|
||||||
visualY += m_spacing;
|
|
||||||
hasVisItem = true;
|
hasVisItem = true;
|
||||||
}
|
}
|
||||||
if (i == idx)
|
if (i == idx) break;
|
||||||
break;
|
if (h > 0) visualY += h;
|
||||||
if (h > 0)
|
|
||||||
visualY += h;
|
|
||||||
}
|
}
|
||||||
item->setY(visualY - m_contentY);
|
item->setY(visualY - m_contentY);
|
||||||
}
|
}
|
||||||
@@ -397,8 +364,8 @@ void LazyListView::updatePolish() {
|
|||||||
|
|
||||||
// Animate from visual position to layout position
|
// Animate from visual position to layout position
|
||||||
if (idx >= 0 && idx < static_cast<int>(m_layout.size()))
|
if (idx >= 0 && idx < static_cast<int>(m_layout.size()))
|
||||||
item->setProperty("y",
|
item->setProperty(
|
||||||
m_layout[idx].targetY - m_contentY);
|
"y", m_layout[idx].targetY - m_contentY);
|
||||||
|
|
||||||
polish();
|
polish();
|
||||||
});
|
});
|
||||||
@@ -432,8 +399,7 @@ void LazyListView::updatePolish() {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
const int idx = entry.modelIndex;
|
const int idx = entry.modelIndex;
|
||||||
if (idx < 0 || idx >= layoutSize)
|
if (idx < 0 || idx >= layoutSize) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height))
|
if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height))
|
||||||
continue;
|
continue;
|
||||||
@@ -444,8 +410,6 @@ void LazyListView::updatePolish() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Layout Engine ---
|
|
||||||
|
|
||||||
void LazyListView::relayout() {
|
void LazyListView::relayout() {
|
||||||
// Layout positioning uses preferredHeight (final/non-animated).
|
// Layout positioning uses preferredHeight (final/non-animated).
|
||||||
// Only add spacing between items with non-zero height.
|
// Only add spacing between items with non-zero height.
|
||||||
@@ -455,8 +419,7 @@ void LazyListView::relayout() {
|
|||||||
const qreal layoutH = record.heightKnown ? record.height
|
const qreal layoutH = record.heightKnown ? record.height
|
||||||
: effectiveEstimatedHeight();
|
: effectiveEstimatedHeight();
|
||||||
if (layoutH > 0) {
|
if (layoutH > 0) {
|
||||||
if (hasLayoutItem)
|
if (hasLayoutItem) y += m_spacing;
|
||||||
y += m_spacing;
|
|
||||||
hasLayoutItem = true;
|
hasLayoutItem = true;
|
||||||
record.targetY = y;
|
record.targetY = y;
|
||||||
y += layoutH;
|
y += layoutH;
|
||||||
@@ -483,8 +446,7 @@ void LazyListView::relayout() {
|
|||||||
h = m_layout[i].heightKnown ? m_layout[i].height
|
h = m_layout[i].heightKnown ? m_layout[i].height
|
||||||
: effectiveEstimatedHeight();
|
: effectiveEstimatedHeight();
|
||||||
if (h > 0) {
|
if (h > 0) {
|
||||||
if (hasVisItem)
|
if (hasVisItem) visY += m_spacing;
|
||||||
visY += m_spacing;
|
|
||||||
hasVisItem = true;
|
hasVisItem = true;
|
||||||
visY += h;
|
visY += h;
|
||||||
}
|
}
|
||||||
@@ -492,11 +454,9 @@ void LazyListView::relayout() {
|
|||||||
|
|
||||||
// Account for dying delegates still visually present
|
// Account for dying delegates still visually present
|
||||||
for (const auto& dying : std::as_const(m_dyingDelegates)) {
|
for (const auto& dying : std::as_const(m_dyingDelegates)) {
|
||||||
if (!dying.item)
|
if (!dying.item) continue;
|
||||||
continue;
|
|
||||||
const qreal dyingH = delegateVisibleHeight(dying.item);
|
const qreal dyingH = delegateVisibleHeight(dying.item);
|
||||||
if (dyingH > 0)
|
if (dyingH > 0) visY = std::max(visY, dying.item->y() + dyingH);
|
||||||
visY = std::max(visY, dying.item->y() + dyingH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!qFuzzyCompare(m_contentHeight + 1.0, visY + 1.0)) {
|
if (!qFuzzyCompare(m_contentHeight + 1.0, visY + 1.0)) {
|
||||||
@@ -519,8 +479,7 @@ QRectF LazyListView::effectiveViewport() const {
|
|||||||
if (!m_useCustomViewport && m_layoutHeight > 0) {
|
if (!m_useCustomViewport && m_layoutHeight > 0) {
|
||||||
const qreal top = std::min(vp.y(), m_layoutHeight);
|
const qreal top = std::min(vp.y(), m_layoutHeight);
|
||||||
const qreal bottom = std::max(vp.y() + vp.height(), 0.0);
|
const qreal bottom = std::max(vp.y() + vp.height(), 0.0);
|
||||||
if (bottom > top)
|
if (bottom > top) vp = QRectF(vp.x(), top, vp.width(), bottom - top);
|
||||||
vp = QRectF(vp.x(), top, vp.width(), bottom - top);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vp.adjust(0, -m_cacheBuffer, 0, m_cacheBuffer);
|
vp.adjust(0, -m_cacheBuffer, 0, m_cacheBuffer);
|
||||||
@@ -541,12 +500,10 @@ QRectF LazyListView::effectiveViewport() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<int, int> LazyListView::computeVisibleRange() const {
|
std::pair<int, int> LazyListView::computeVisibleRange() const {
|
||||||
if (m_layout.isEmpty())
|
if (m_layout.isEmpty()) return {-1, -1};
|
||||||
return {-1, -1};
|
|
||||||
|
|
||||||
const auto vp = effectiveViewport();
|
const auto vp = effectiveViewport();
|
||||||
if (vp.isEmpty())
|
if (vp.isEmpty()) return {-1, -1};
|
||||||
return {-1, -1};
|
|
||||||
|
|
||||||
const qreal vpTop = vp.y();
|
const qreal vpTop = vp.y();
|
||||||
const qreal vpBottom = vp.y() + vp.height();
|
const qreal vpBottom = vp.y() + vp.height();
|
||||||
@@ -571,22 +528,18 @@ std::pair<int, int> LazyListView::computeVisibleRange() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first >= static_cast<int>(m_layout.size()))
|
if (first >= static_cast<int>(m_layout.size())) return {-1, -1};
|
||||||
return {-1, -1};
|
|
||||||
|
|
||||||
// Linear scan for last visible item
|
// Linear scan for last visible item
|
||||||
int last = first;
|
int last = first;
|
||||||
for (int i = first; i < static_cast<int>(m_layout.size()); ++i) {
|
for (int i = first; i < static_cast<int>(m_layout.size()); ++i) {
|
||||||
if (m_layout[i].targetY > vpBottom)
|
if (m_layout[i].targetY > vpBottom) break;
|
||||||
break;
|
|
||||||
last = i;
|
last = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {first, last};
|
return {first, last};
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Delegate Lifecycle ---
|
|
||||||
|
|
||||||
void LazyListView::syncDelegates() {
|
void LazyListView::syncDelegates() {
|
||||||
const auto [first, last] = computeVisibleRange();
|
const auto [first, last] = computeVisibleRange();
|
||||||
|
|
||||||
@@ -601,8 +554,7 @@ void LazyListView::syncDelegates() {
|
|||||||
const auto vp = effectiveViewport();
|
const auto vp = effectiveViewport();
|
||||||
QList<int> toRemove;
|
QList<int> toRemove;
|
||||||
for (auto it = m_delegates.begin(); it != m_delegates.end(); ++it) {
|
for (auto it = m_delegates.begin(); it != m_delegates.end(); ++it) {
|
||||||
if (visibleIndices.contains(it.key()))
|
if (visibleIndices.contains(it.key())) continue;
|
||||||
continue;
|
|
||||||
if (!it->item || vp.isEmpty()) {
|
if (!it->item || vp.isEmpty()) {
|
||||||
toRemove.append(it.key());
|
toRemove.append(it.key());
|
||||||
continue;
|
continue;
|
||||||
@@ -622,11 +574,9 @@ void LazyListView::syncDelegates() {
|
|||||||
std::min(destroyBudget, static_cast<int>(toRemove.size())));
|
std::min(destroyBudget, static_cast<int>(toRemove.size())));
|
||||||
int destroyed = 0;
|
int destroyed = 0;
|
||||||
for (int idx : toRemove) {
|
for (int idx : toRemove) {
|
||||||
if (destroyed >= destroyBudget)
|
if (destroyed >= destroyBudget) break;
|
||||||
break;
|
|
||||||
auto entry = m_delegates.take(idx);
|
auto entry = m_delegates.take(idx);
|
||||||
if (entry.item)
|
if (entry.item) m_itemToIndex.remove(entry.item);
|
||||||
m_itemToIndex.remove(entry.item);
|
|
||||||
removedEntries.append(std::move(entry));
|
removedEntries.append(std::move(entry));
|
||||||
++destroyed;
|
++destroyed;
|
||||||
}
|
}
|
||||||
@@ -637,8 +587,7 @@ void LazyListView::syncDelegates() {
|
|||||||
QList<int> toCreate;
|
QList<int> toCreate;
|
||||||
if (first >= 0) {
|
if (first >= 0) {
|
||||||
for (int i = first; i <= last; ++i) {
|
for (int i = first; i <= last; ++i) {
|
||||||
if (!m_delegates.contains(i))
|
if (!m_delegates.contains(i)) toCreate.append(i);
|
||||||
toCreate.append(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,8 +596,7 @@ void LazyListView::syncDelegates() {
|
|||||||
: static_cast<int>(toCreate.size());
|
: static_cast<int>(toCreate.size());
|
||||||
int created = 0;
|
int created = 0;
|
||||||
for (int i : toCreate) {
|
for (int i : toCreate) {
|
||||||
if (created >= createBudget)
|
if (created >= createBudget) break;
|
||||||
break;
|
|
||||||
|
|
||||||
auto entry = createDelegate(i);
|
auto entry = createDelegate(i);
|
||||||
if (entry.item) {
|
if (entry.item) {
|
||||||
@@ -674,25 +622,21 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
DelegateEntry entry;
|
DelegateEntry entry;
|
||||||
entry.modelIndex = modelIndex;
|
entry.modelIndex = modelIndex;
|
||||||
|
|
||||||
if (!m_delegate || !m_model)
|
if (!m_delegate || !m_model) return entry;
|
||||||
return entry;
|
|
||||||
|
|
||||||
const auto roleNames = m_model->roleNames();
|
const auto roleNames = m_model->roleNames();
|
||||||
|
|
||||||
// Use the delegate component's creation context for beginCreate
|
// Use the delegate component's creation context for beginCreate
|
||||||
// so bound components (pragma ComponentBehavior: Bound) are accepted.
|
// so bound components (pragma ComponentBehavior: Bound) are accepted.
|
||||||
auto* compContext = m_delegate->creationContext();
|
auto* compContext = m_delegate->creationContext();
|
||||||
if (!compContext)
|
if (!compContext) compContext = qmlContext(this);
|
||||||
compContext = qmlContext(this);
|
if (!compContext) return entry;
|
||||||
if (!compContext)
|
|
||||||
return entry;
|
|
||||||
|
|
||||||
auto* obj = m_delegate->beginCreate(compContext);
|
auto* obj = m_delegate->beginCreate(compContext);
|
||||||
entry.item = qobject_cast<QQuickItem*>(obj);
|
entry.item = qobject_cast<QQuickItem*>(obj);
|
||||||
|
|
||||||
if (!entry.item) {
|
if (!entry.item) {
|
||||||
if (obj)
|
if (obj) m_delegate->completeCreate();
|
||||||
m_delegate->completeCreate();
|
|
||||||
delete obj;
|
delete obj;
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -705,16 +649,15 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
|
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
|
||||||
const auto name = QString::fromUtf8(it.value());
|
const auto name = QString::fromUtf8(it.value());
|
||||||
initialProps.insert(name, m_model->data(index, it.key()));
|
initialProps.insert(name, m_model->data(index, it.key()));
|
||||||
if (name == QStringLiteral("modelData"))
|
if (name == QStringLiteral("modelData")) hasModelData = true;
|
||||||
hasModelData = true;
|
|
||||||
}
|
}
|
||||||
initialProps.insert(QStringLiteral("index"), modelIndex);
|
initialProps.insert(QStringLiteral("index"), modelIndex);
|
||||||
|
|
||||||
if (!hasModelData) {
|
if (!hasModelData) {
|
||||||
const auto role = roleNames.isEmpty() ? Qt::DisplayRole
|
const auto role = roleNames.isEmpty() ? Qt::DisplayRole
|
||||||
: roleNames.constBegin().key();
|
: roleNames.constBegin().key();
|
||||||
initialProps.insert(QStringLiteral("modelData"),
|
initialProps.insert(
|
||||||
m_model->data(index, role));
|
QStringLiteral("modelData"), m_model->data(index, role));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_delegate->setInitialProperties(entry.item, initialProps);
|
m_delegate->setInitialProperties(entry.item, initialProps);
|
||||||
@@ -728,8 +671,7 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
m_layout[modelIndex].isNew) {
|
m_layout[modelIndex].isNew) {
|
||||||
auto* addingAttached = qobject_cast<LazyListViewAttached*>(
|
auto* addingAttached = qobject_cast<LazyListViewAttached*>(
|
||||||
qmlAttachedPropertiesObject<LazyListView>(entry.item, true));
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, true));
|
||||||
if (addingAttached)
|
if (addingAttached) addingAttached->setAdding(true);
|
||||||
addingAttached->setAdding(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_delegate->completeCreate();
|
m_delegate->completeCreate();
|
||||||
@@ -740,15 +682,12 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
// Height-change handler — uses m_itemToIndex for O(1) lookup.
|
// Height-change handler — uses m_itemToIndex for O(1) lookup.
|
||||||
// Ignored while the delegate is not yet ready.
|
// Ignored while the delegate is not yet ready.
|
||||||
auto onHeightChanged = [this, item = entry.item] {
|
auto onHeightChanged = [this, item = entry.item] {
|
||||||
if (!isDelegateReady(item))
|
if (!isDelegateReady(item)) return;
|
||||||
return;
|
|
||||||
auto indexIt = m_itemToIndex.find(item);
|
auto indexIt = m_itemToIndex.find(item);
|
||||||
if (indexIt == m_itemToIndex.end())
|
if (indexIt == m_itemToIndex.end()) return;
|
||||||
return;
|
|
||||||
const int idx = indexIt.value();
|
const int idx = indexIt.value();
|
||||||
auto delegateIt = m_delegates.find(idx);
|
auto delegateIt = m_delegates.find(idx);
|
||||||
if (delegateIt == m_delegates.end() || delegateIt->item != item)
|
if (delegateIt == m_delegates.end() || delegateIt->item != item) return;
|
||||||
return;
|
|
||||||
const qreal h = delegateHeight(item);
|
const qreal h = delegateHeight(item);
|
||||||
if (idx < static_cast<int>(m_layout.size()) &&
|
if (idx < static_cast<int>(m_layout.size()) &&
|
||||||
!qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) {
|
!qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) {
|
||||||
@@ -756,8 +695,7 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
const bool wasKnown = m_layout[idx].heightKnown;
|
const bool wasKnown = m_layout[idx].heightKnown;
|
||||||
m_layout[idx].height = h;
|
m_layout[idx].height = h;
|
||||||
m_layout[idx].heightKnown = true;
|
m_layout[idx].heightKnown = true;
|
||||||
if (wasKnown)
|
if (wasKnown) untrackHeight(oldH);
|
||||||
untrackHeight(oldH);
|
|
||||||
trackHeight(h);
|
trackHeight(h);
|
||||||
|
|
||||||
// If this tracked item is above the viewport, emit a
|
// If this tracked item is above the viewport, emit a
|
||||||
@@ -785,58 +723,56 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Watch implicitHeight as fallback
|
// Watch implicitHeight as fallback
|
||||||
connect(entry.item,
|
connect(
|
||||||
&QQuickItem::implicitHeightChanged,
|
entry.item, &QQuickItem::implicitHeightChanged, this, onHeightChanged);
|
||||||
this,
|
|
||||||
onHeightChanged);
|
|
||||||
|
|
||||||
// Watch attached properties if the delegate uses them
|
// Watch attached properties if the delegate uses them
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
||||||
if (attached) {
|
if (attached) {
|
||||||
connect(attached,
|
connect(
|
||||||
&LazyListViewAttached::preferredHeightChanged,
|
attached,
|
||||||
this,
|
&LazyListViewAttached::preferredHeightChanged,
|
||||||
onHeightChanged);
|
this,
|
||||||
connect(attached,
|
onHeightChanged);
|
||||||
&LazyListViewAttached::visibleHeightChanged,
|
connect(
|
||||||
this,
|
attached,
|
||||||
[this] { polish(); });
|
&LazyListViewAttached::visibleHeightChanged,
|
||||||
connect(attached,
|
this,
|
||||||
&LazyListViewAttached::readyChanged,
|
[this] { polish(); });
|
||||||
this,
|
connect(
|
||||||
[this, item = entry.item] {
|
attached,
|
||||||
auto indexIt = m_itemToIndex.find(item);
|
&LazyListViewAttached::readyChanged,
|
||||||
if (indexIt == m_itemToIndex.end())
|
this,
|
||||||
return;
|
[this, item = entry.item] {
|
||||||
const int idx = indexIt.value();
|
auto indexIt = m_itemToIndex.find(item);
|
||||||
if (idx >= static_cast<int>(m_layout.size()))
|
if (indexIt == m_itemToIndex.end()) return;
|
||||||
return;
|
const int idx = indexIt.value();
|
||||||
auto* att = qobject_cast<LazyListViewAttached*>(
|
if (idx >= static_cast<int>(m_layout.size())) return;
|
||||||
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
auto* att = qobject_cast<LazyListViewAttached*>(
|
||||||
if (!att || !att->ready())
|
qmlAttachedPropertiesObject<LazyListView>(item, false));
|
||||||
return;
|
if (!att || !att->ready()) return;
|
||||||
|
|
||||||
const qreal h = delegateHeight(item);
|
const qreal h = delegateHeight(item);
|
||||||
const qreal oldLayoutH = m_layout[idx].heightKnown
|
const qreal oldLayoutH = m_layout[idx].heightKnown
|
||||||
? m_layout[idx].height
|
? m_layout[idx].height
|
||||||
: effectiveEstimatedHeight();
|
: effectiveEstimatedHeight();
|
||||||
if (m_layout[idx].heightKnown)
|
if (m_layout[idx].heightKnown)
|
||||||
untrackHeight(m_layout[idx].height);
|
untrackHeight(m_layout[idx].height);
|
||||||
m_layout[idx].height = h;
|
m_layout[idx].height = h;
|
||||||
m_layout[idx].heightKnown = true;
|
m_layout[idx].heightKnown = true;
|
||||||
trackHeight(h);
|
trackHeight(h);
|
||||||
|
|
||||||
if (att->trackViewport() &&
|
if (att->trackViewport() &&
|
||||||
!qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) {
|
!qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) {
|
||||||
const qreal vpTop = m_useCustomViewport ? m_viewport.y()
|
const qreal vpTop = m_useCustomViewport ? m_viewport.y()
|
||||||
: m_contentY;
|
: m_contentY;
|
||||||
if (m_layout[idx].targetY < vpTop)
|
if (m_layout[idx].targetY < vpTop)
|
||||||
emit viewportAdjustNeeded(h - oldLayoutH);
|
emit viewportAdjustNeeded(h - oldLayoutH);
|
||||||
}
|
}
|
||||||
|
|
||||||
polish();
|
polish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
@@ -852,8 +788,7 @@ void LazyListView::destroyDelegate(DelegateEntry& entry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::updateDelegateData(DelegateEntry& entry) {
|
void LazyListView::updateDelegateData(DelegateEntry& entry) {
|
||||||
if (!m_model || !entry.item)
|
if (!m_model || !entry.item) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const auto roleNames = m_model->roleNames();
|
const auto roleNames = m_model->roleNames();
|
||||||
const auto index = m_model->index(entry.modelIndex, 0);
|
const auto index = m_model->index(entry.modelIndex, 0);
|
||||||
@@ -861,10 +796,9 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) {
|
|||||||
|
|
||||||
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
|
for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) {
|
||||||
const auto name = QString::fromUtf8(it.value());
|
const auto name = QString::fromUtf8(it.value());
|
||||||
entry.item->setProperty(name.toUtf8().constData(),
|
entry.item->setProperty(
|
||||||
m_model->data(index, it.key()));
|
name.toUtf8().constData(), m_model->data(index, it.key()));
|
||||||
if (name == QStringLiteral("modelData"))
|
if (name == QStringLiteral("modelData")) hasModelData = true;
|
||||||
hasModelData = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.item->setProperty("index", entry.modelIndex);
|
entry.item->setProperty("index", entry.modelIndex);
|
||||||
@@ -876,53 +810,58 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Model Connection ---
|
|
||||||
|
|
||||||
void LazyListView::connectModel() {
|
void LazyListView::connectModel() {
|
||||||
if (!m_model)
|
if (!m_model) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_modelConnections = {
|
m_modelConnections = {
|
||||||
connect(m_model,
|
connect(
|
||||||
&QAbstractItemModel::rowsInserted,
|
m_model,
|
||||||
this,
|
&QAbstractItemModel::rowsInserted,
|
||||||
&LazyListView::onRowsInserted),
|
this,
|
||||||
connect(m_model,
|
&LazyListView::onRowsInserted),
|
||||||
&QAbstractItemModel::rowsAboutToBeRemoved,
|
connect(
|
||||||
this,
|
m_model,
|
||||||
&LazyListView::onRowsAboutToBeRemoved),
|
&QAbstractItemModel::rowsAboutToBeRemoved,
|
||||||
connect(m_model,
|
this,
|
||||||
&QAbstractItemModel::rowsRemoved,
|
&LazyListView::onRowsAboutToBeRemoved),
|
||||||
this,
|
connect(
|
||||||
&LazyListView::onRowsRemoved),
|
m_model,
|
||||||
connect(m_model,
|
&QAbstractItemModel::rowsRemoved,
|
||||||
&QAbstractItemModel::rowsMoved,
|
this,
|
||||||
this,
|
&LazyListView::onRowsRemoved),
|
||||||
&LazyListView::onRowsMoved),
|
connect(
|
||||||
connect(m_model,
|
m_model,
|
||||||
&QAbstractItemModel::dataChanged,
|
&QAbstractItemModel::rowsMoved,
|
||||||
this,
|
this,
|
||||||
&LazyListView::onDataChanged),
|
&LazyListView::onRowsMoved),
|
||||||
connect(m_model,
|
connect(
|
||||||
&QAbstractItemModel::modelReset,
|
m_model,
|
||||||
this,
|
&QAbstractItemModel::dataChanged,
|
||||||
&LazyListView::onModelReset),
|
this,
|
||||||
connect(m_model,
|
&LazyListView::onDataChanged),
|
||||||
&QAbstractItemModel::layoutChanged,
|
connect(
|
||||||
this,
|
m_model,
|
||||||
[this] {
|
&QAbstractItemModel::modelReset,
|
||||||
for (auto& entry : m_delegates)
|
this,
|
||||||
updateDelegateData(entry);
|
&LazyListView::onModelReset),
|
||||||
polish();
|
connect(
|
||||||
}),
|
m_model,
|
||||||
connect(m_model,
|
&QAbstractItemModel::layoutChanged,
|
||||||
&QObject::destroyed,
|
this,
|
||||||
this,
|
[this] {
|
||||||
[this] {
|
for (auto& entry : m_delegates)
|
||||||
m_model = nullptr;
|
updateDelegateData(entry);
|
||||||
resetContent();
|
polish();
|
||||||
emit modelChanged();
|
}),
|
||||||
}),
|
connect(
|
||||||
|
m_model,
|
||||||
|
&QObject::destroyed,
|
||||||
|
this,
|
||||||
|
[this] {
|
||||||
|
m_model = nullptr;
|
||||||
|
resetContent();
|
||||||
|
emit modelChanged();
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,11 +901,9 @@ void LazyListView::resetContent() {
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsInserted(const QModelIndex& parent,
|
void LazyListView::onRowsInserted(
|
||||||
int first,
|
const QModelIndex& parent, int first, int last) {
|
||||||
int last) {
|
if (parent.isValid()) return;
|
||||||
if (parent.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const int insertCount = last - first + 1;
|
const int insertCount = last - first + 1;
|
||||||
// Insert new layout records
|
// Insert new layout records
|
||||||
@@ -990,19 +927,15 @@ void LazyListView::onRowsInserted(const QModelIndex& parent,
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent,
|
void LazyListView::onRowsAboutToBeRemoved(
|
||||||
int first,
|
const QModelIndex& parent, int first, int last) {
|
||||||
int last) {
|
if (parent.isValid()) return;
|
||||||
if (parent.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = first; i <= last; ++i) {
|
for (int i = first; i <= last; ++i) {
|
||||||
if (!m_delegates.contains(i))
|
if (!m_delegates.contains(i)) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
auto entry = m_delegates.take(i);
|
auto entry = m_delegates.take(i);
|
||||||
if (entry.item)
|
if (entry.item) m_itemToIndex.remove(entry.item);
|
||||||
m_itemToIndex.remove(entry.item);
|
|
||||||
entry.pendingRemoval = true;
|
entry.pendingRemoval = true;
|
||||||
|
|
||||||
// Never made visible — skip remove animation
|
// Never made visible — skip remove animation
|
||||||
@@ -1014,8 +947,7 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent,
|
|||||||
if (m_removeDuration > 0 && entry.item) {
|
if (m_removeDuration > 0 && entry.item) {
|
||||||
auto* attached = qobject_cast<LazyListViewAttached*>(
|
auto* attached = qobject_cast<LazyListViewAttached*>(
|
||||||
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
qmlAttachedPropertiesObject<LazyListView>(entry.item, false));
|
||||||
if (attached)
|
if (attached) attached->setRemoving(true);
|
||||||
attached->setRemoving(true);
|
|
||||||
|
|
||||||
// Schedule destruction after the remove animation duration
|
// Schedule destruction after the remove animation duration
|
||||||
auto* item = entry.item;
|
auto* item = entry.item;
|
||||||
@@ -1037,18 +969,15 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsRemoved(const QModelIndex& parent,
|
void LazyListView::onRowsRemoved(
|
||||||
int first,
|
const QModelIndex& parent, int first, int last) {
|
||||||
int last) {
|
if (parent.isValid()) return;
|
||||||
if (parent.isValid())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const int removeCount = last - first + 1;
|
const int removeCount = last - first + 1;
|
||||||
|
|
||||||
// Untrack known heights being removed
|
// Untrack known heights being removed
|
||||||
for (int i = first; i <= last; ++i) {
|
for (int i = first; i <= last; ++i) {
|
||||||
if (m_layout[i].heightKnown)
|
if (m_layout[i].heightKnown) untrackHeight(m_layout[i].height);
|
||||||
untrackHeight(m_layout[i].height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove layout records
|
// Remove layout records
|
||||||
@@ -1072,13 +1001,13 @@ void LazyListView::onRowsRemoved(const QModelIndex& parent,
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onRowsMoved(const QModelIndex& parent,
|
void LazyListView::onRowsMoved(
|
||||||
int start,
|
const QModelIndex& parent,
|
||||||
int end,
|
int start,
|
||||||
const QModelIndex& destination,
|
int end,
|
||||||
int row) {
|
const QModelIndex& destination,
|
||||||
if (parent.isValid() || destination.isValid())
|
int row) {
|
||||||
return;
|
if (parent.isValid() || destination.isValid()) return;
|
||||||
|
|
||||||
const int count = end - start + 1;
|
const int count = end - start + 1;
|
||||||
const int dest = row > start ? row - count : row;
|
const int dest = row > start ? row - count : row;
|
||||||
@@ -1101,10 +1030,8 @@ void LazyListView::onRowsMoved(const QModelIndex& parent,
|
|||||||
if (oldIdx >= start && oldIdx <= end) {
|
if (oldIdx >= start && oldIdx <= end) {
|
||||||
newIdx = dest + (oldIdx - start);
|
newIdx = dest + (oldIdx - start);
|
||||||
} else {
|
} else {
|
||||||
if (oldIdx > end)
|
if (oldIdx > end) newIdx -= count;
|
||||||
newIdx -= count;
|
if (newIdx >= dest) newIdx += count;
|
||||||
if (newIdx >= dest)
|
|
||||||
newIdx += count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto entry = std::move(it.value());
|
auto entry = std::move(it.value());
|
||||||
@@ -1120,17 +1047,16 @@ void LazyListView::onRowsMoved(const QModelIndex& parent,
|
|||||||
polish();
|
polish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LazyListView::onDataChanged(const QModelIndex& topLeft,
|
void LazyListView::onDataChanged(
|
||||||
const QModelIndex& bottomRight,
|
const QModelIndex& topLeft,
|
||||||
const QList<int>& roles) {
|
const QModelIndex& bottomRight,
|
||||||
|
const QList<int>& roles) {
|
||||||
Q_UNUSED(roles)
|
Q_UNUSED(roles)
|
||||||
|
|
||||||
if (topLeft.parent().isValid())
|
if (topLeft.parent().isValid()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
|
for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
|
||||||
if (m_delegates.contains(i))
|
if (m_delegates.contains(i)) updateDelegateData(m_delegates[i]);
|
||||||
updateDelegateData(m_delegates[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,232 +12,265 @@
|
|||||||
namespace ZShell::components {
|
namespace ZShell::components {
|
||||||
|
|
||||||
class LazyListViewAttached : public QObject {
|
class LazyListViewAttached : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY visibleHeightChanged)
|
qreal preferredHeight READ preferredHeight WRITE setPreferredHeight
|
||||||
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
|
NOTIFY preferredHeightChanged)
|
||||||
Q_PROPERTY(bool adding READ adding NOTIFY addingChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(bool removing READ removing NOTIFY removingChanged)
|
qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY
|
||||||
Q_PROPERTY(bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY trackViewportChanged)
|
visibleHeightChanged)
|
||||||
|
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
|
||||||
|
Q_PROPERTY(bool adding READ adding NOTIFY addingChanged)
|
||||||
|
Q_PROPERTY(bool removing READ removing NOTIFY removingChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY
|
||||||
|
trackViewportChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LazyListViewAttached(QObject* parent = nullptr);
|
explicit LazyListViewAttached(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] qreal preferredHeight() const;
|
[[nodiscard]] qreal preferredHeight() const;
|
||||||
void setPreferredHeight(qreal height);
|
void setPreferredHeight(qreal height);
|
||||||
|
|
||||||
[[nodiscard]] qreal visibleHeight() const;
|
[[nodiscard]] qreal visibleHeight() const;
|
||||||
void setVisibleHeight(qreal height);
|
void setVisibleHeight(qreal height);
|
||||||
|
|
||||||
[[nodiscard]] bool ready() const;
|
[[nodiscard]] bool ready() const;
|
||||||
void setReady(bool ready);
|
void setReady(bool ready);
|
||||||
|
|
||||||
[[nodiscard]] bool adding() const;
|
[[nodiscard]] bool adding() const;
|
||||||
void setAdding(bool adding);
|
void setAdding(bool adding);
|
||||||
|
|
||||||
[[nodiscard]] bool removing() const;
|
[[nodiscard]] bool removing() const;
|
||||||
void setRemoving(bool removing);
|
void setRemoving(bool removing);
|
||||||
|
|
||||||
[[nodiscard]] bool trackViewport() const;
|
[[nodiscard]] bool trackViewport() const;
|
||||||
void setTrackViewport(bool track);
|
void setTrackViewport(bool track);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void preferredHeightChanged();
|
void preferredHeightChanged();
|
||||||
void visibleHeightChanged();
|
void visibleHeightChanged();
|
||||||
void readyChanged();
|
void readyChanged();
|
||||||
void addingChanged();
|
void addingChanged();
|
||||||
void removingChanged();
|
void removingChanged();
|
||||||
void trackViewportChanged();
|
void trackViewportChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qreal m_preferredHeight = -1;
|
qreal m_preferredHeight = -1;
|
||||||
qreal m_visibleHeight = -1;
|
qreal m_visibleHeight = -1;
|
||||||
bool m_ready = false;
|
bool m_ready = false;
|
||||||
bool m_adding = false;
|
bool m_adding = false;
|
||||||
bool m_removing = false;
|
bool m_removing = false;
|
||||||
bool m_trackViewport = false;
|
bool m_trackViewport = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LazyListView : public QQuickItem {
|
class LazyListView : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_ATTACHED(LazyListViewAttached)
|
QML_ATTACHED(LazyListViewAttached)
|
||||||
|
|
||||||
// Model & Delegate
|
// Model & Delegate
|
||||||
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
|
QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY
|
||||||
|
delegateChanged)
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||||
Q_PROPERTY(qreal contentHeight READ contentHeight NOTIFY contentHeightChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged)
|
qreal contentHeight READ contentHeight NOTIFY contentHeightChanged)
|
||||||
Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged)
|
Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged)
|
||||||
|
|
||||||
// Viewport & Lazy Loading
|
// Viewport & Lazy Loading
|
||||||
Q_PROPERTY(QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport NOTIFY useCustomViewportChanged)
|
QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged)
|
||||||
Q_PROPERTY(qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged)
|
Q_PROPERTY(
|
||||||
|
bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport
|
||||||
|
NOTIFY useCustomViewportChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY
|
||||||
|
cacheBufferChanged)
|
||||||
|
|
||||||
// Sizing
|
// Sizing
|
||||||
Q_PROPERTY(qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight NOTIFY estimatedHeightChanged)
|
Q_PROPERTY(
|
||||||
|
qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight
|
||||||
|
NOTIFY estimatedHeightChanged)
|
||||||
|
|
||||||
// Async
|
// Async
|
||||||
Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged)
|
Q_PROPERTY(
|
||||||
|
bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY
|
||||||
|
asynchronousChanged)
|
||||||
|
|
||||||
// Animation Durations
|
// Animation Durations
|
||||||
Q_PROPERTY(int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY removeDurationChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY readyDelayChanged)
|
int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY
|
||||||
|
removeDurationChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY
|
||||||
|
readyDelayChanged)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LazyListView(QQuickItem* parent = nullptr);
|
explicit LazyListView(QQuickItem* parent = nullptr);
|
||||||
~LazyListView() override;
|
~LazyListView() override;
|
||||||
|
|
||||||
static LazyListViewAttached* qmlAttachedProperties(QObject* object);
|
static LazyListViewAttached* qmlAttachedProperties(QObject* object);
|
||||||
|
|
||||||
// Model & Delegate
|
// Model & Delegate
|
||||||
[[nodiscard]] QAbstractItemModel* model() const;
|
[[nodiscard]] QAbstractItemModel* model() const;
|
||||||
void setModel(QAbstractItemModel* model);
|
void setModel(QAbstractItemModel* model);
|
||||||
|
|
||||||
[[nodiscard]] QQmlComponent* delegate() const;
|
[[nodiscard]] QQmlComponent* delegate() const;
|
||||||
void setDelegate(QQmlComponent* delegate);
|
void setDelegate(QQmlComponent* delegate);
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
[[nodiscard]] qreal spacing() const;
|
[[nodiscard]] qreal spacing() const;
|
||||||
void setSpacing(qreal spacing);
|
void setSpacing(qreal spacing);
|
||||||
|
|
||||||
[[nodiscard]] qreal contentHeight() const;
|
[[nodiscard]] qreal contentHeight() const;
|
||||||
[[nodiscard]] qreal layoutHeight() const;
|
[[nodiscard]] qreal layoutHeight() const;
|
||||||
|
|
||||||
[[nodiscard]] qreal contentY() const;
|
[[nodiscard]] qreal contentY() const;
|
||||||
void setContentY(qreal contentY);
|
void setContentY(qreal contentY);
|
||||||
|
|
||||||
// Viewport
|
// Viewport
|
||||||
[[nodiscard]] QRectF viewport() const;
|
[[nodiscard]] QRectF viewport() const;
|
||||||
void setViewport(const QRectF& viewport);
|
void setViewport(const QRectF& viewport);
|
||||||
|
|
||||||
[[nodiscard]] bool useCustomViewport() const;
|
[[nodiscard]] bool useCustomViewport() const;
|
||||||
void setUseCustomViewport(bool use);
|
void setUseCustomViewport(bool use);
|
||||||
|
|
||||||
[[nodiscard]] qreal cacheBuffer() const;
|
[[nodiscard]] qreal cacheBuffer() const;
|
||||||
void setCacheBuffer(qreal buffer);
|
void setCacheBuffer(qreal buffer);
|
||||||
|
|
||||||
// Sizing
|
// Sizing
|
||||||
[[nodiscard]] qreal estimatedHeight() const;
|
[[nodiscard]] qreal estimatedHeight() const;
|
||||||
void setEstimatedHeight(qreal height);
|
void setEstimatedHeight(qreal height);
|
||||||
|
|
||||||
// Async
|
// Async
|
||||||
[[nodiscard]] bool asynchronous() const;
|
[[nodiscard]] bool asynchronous() const;
|
||||||
void setAsynchronous(bool async);
|
void setAsynchronous(bool async);
|
||||||
|
|
||||||
// Animation Durations
|
// Animation Durations
|
||||||
[[nodiscard]] int removeDuration() const;
|
[[nodiscard]] int removeDuration() const;
|
||||||
void setRemoveDuration(int duration);
|
void setRemoveDuration(int duration);
|
||||||
|
|
||||||
[[nodiscard]] int readyDelay() const;
|
[[nodiscard]] int readyDelay() const;
|
||||||
void setReadyDelay(int delay);
|
void setReadyDelay(int delay);
|
||||||
|
|
||||||
// State
|
// State
|
||||||
[[nodiscard]] int count() const;
|
[[nodiscard]] int count() const;
|
||||||
signals:
|
signals:
|
||||||
void modelChanged();
|
void modelChanged();
|
||||||
void delegateChanged();
|
void delegateChanged();
|
||||||
void spacingChanged();
|
void spacingChanged();
|
||||||
void contentHeightChanged();
|
void contentHeightChanged();
|
||||||
void layoutHeightChanged();
|
void layoutHeightChanged();
|
||||||
void contentYChanged();
|
void contentYChanged();
|
||||||
void viewportChanged();
|
void viewportChanged();
|
||||||
void useCustomViewportChanged();
|
void useCustomViewportChanged();
|
||||||
void cacheBufferChanged();
|
void cacheBufferChanged();
|
||||||
void estimatedHeightChanged();
|
void estimatedHeightChanged();
|
||||||
void asynchronousChanged();
|
void asynchronousChanged();
|
||||||
void removeDurationChanged();
|
void removeDurationChanged();
|
||||||
void readyDelayChanged();
|
void readyDelayChanged();
|
||||||
void countChanged();
|
void countChanged();
|
||||||
void viewportAdjustNeeded(qreal delta);
|
void viewportAdjustNeeded(qreal delta);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void componentComplete() override;
|
void componentComplete() override;
|
||||||
void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
void geometryChange(
|
||||||
void updatePolish() override;
|
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||||
|
void updatePolish() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ItemRecord {
|
struct ItemRecord {
|
||||||
qreal targetY = 0;
|
qreal targetY = 0;
|
||||||
qreal height = 0;
|
qreal height = 0;
|
||||||
bool heightKnown = false;
|
bool heightKnown = false;
|
||||||
bool isNew = false;
|
bool isNew = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DelegateEntry {
|
struct DelegateEntry {
|
||||||
int modelIndex = -1;
|
int modelIndex = -1;
|
||||||
QQuickItem* item = nullptr;
|
QQuickItem* item = nullptr;
|
||||||
bool pendingRemoval = false;
|
bool pendingRemoval = false;
|
||||||
bool pendingInsert = false;
|
bool pendingInsert = false;
|
||||||
bool readyDelayStarted = false;
|
bool readyDelayStarted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
void relayout();
|
void relayout();
|
||||||
[[nodiscard]] std::pair<int, int> computeVisibleRange() const;
|
[[nodiscard]] std::pair<int, int> computeVisibleRange() const;
|
||||||
[[nodiscard]] QRectF effectiveViewport() const;
|
[[nodiscard]] QRectF effectiveViewport() const;
|
||||||
[[nodiscard]] qreal effectiveEstimatedHeight() const;
|
[[nodiscard]] qreal effectiveEstimatedHeight() const;
|
||||||
[[nodiscard]] static qreal delegateHeight(QQuickItem* item);
|
[[nodiscard]] static qreal delegateHeight(QQuickItem* item);
|
||||||
[[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item);
|
[[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item);
|
||||||
[[nodiscard]] static bool isDelegateReady(QQuickItem* item);
|
[[nodiscard]] static bool isDelegateReady(QQuickItem* item);
|
||||||
void trackHeight(qreal height);
|
void trackHeight(qreal height);
|
||||||
void untrackHeight(qreal height);
|
void untrackHeight(qreal height);
|
||||||
|
|
||||||
// Delegate lifecycle
|
// Delegate lifecycle
|
||||||
void syncDelegates();
|
void syncDelegates();
|
||||||
DelegateEntry createDelegate(int modelIndex);
|
DelegateEntry createDelegate(int modelIndex);
|
||||||
void destroyDelegate(DelegateEntry& entry);
|
void destroyDelegate(DelegateEntry& entry);
|
||||||
void updateDelegateData(DelegateEntry& entry);
|
void updateDelegateData(DelegateEntry& entry);
|
||||||
|
|
||||||
// Model connection
|
// Model connection
|
||||||
void connectModel();
|
void connectModel();
|
||||||
void disconnectModel();
|
void disconnectModel();
|
||||||
void resetContent();
|
void resetContent();
|
||||||
void onRowsInserted(const QModelIndex& parent, int first, int last);
|
void onRowsInserted(const QModelIndex& parent, int first, int last);
|
||||||
void onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last);
|
void onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last);
|
||||||
void onRowsRemoved(const QModelIndex& parent, int first, int last);
|
void onRowsRemoved(const QModelIndex& parent, int first, int last);
|
||||||
void onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row);
|
void onRowsMoved(
|
||||||
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList<int>& roles);
|
const QModelIndex& parent,
|
||||||
void onModelReset();
|
int start,
|
||||||
|
int end,
|
||||||
|
const QModelIndex& destination,
|
||||||
|
int row);
|
||||||
|
void onDataChanged(
|
||||||
|
const QModelIndex& topLeft,
|
||||||
|
const QModelIndex& bottomRight,
|
||||||
|
const QList<int>& roles);
|
||||||
|
void onModelReset();
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
QAbstractItemModel* m_model = nullptr;
|
QAbstractItemModel* m_model = nullptr;
|
||||||
QQmlComponent* m_delegate = nullptr;
|
QQmlComponent* m_delegate = nullptr;
|
||||||
|
|
||||||
qreal m_spacing = 0;
|
qreal m_spacing = 0;
|
||||||
qreal m_contentHeight = 0;
|
qreal m_contentHeight = 0;
|
||||||
qreal m_layoutHeight = 0;
|
qreal m_layoutHeight = 0;
|
||||||
qreal m_contentY = 0;
|
qreal m_contentY = 0;
|
||||||
|
|
||||||
QRectF m_viewport;
|
QRectF m_viewport;
|
||||||
bool m_useCustomViewport = false;
|
bool m_useCustomViewport = false;
|
||||||
qreal m_cacheBuffer = 0;
|
qreal m_cacheBuffer = 0;
|
||||||
|
|
||||||
qreal m_estimatedHeight = -1;
|
qreal m_estimatedHeight = -1;
|
||||||
qreal m_knownHeightSum = 0;
|
qreal m_knownHeightSum = 0;
|
||||||
int m_knownHeightCount = 0;
|
int m_knownHeightCount = 0;
|
||||||
bool m_asynchronous = false;
|
bool m_asynchronous = false;
|
||||||
|
|
||||||
int m_removeDuration = 300;
|
int m_removeDuration = 300;
|
||||||
int m_readyDelay = 0;
|
int m_readyDelay = 0;
|
||||||
|
|
||||||
QVector<ItemRecord> m_layout;
|
QVector<ItemRecord> m_layout;
|
||||||
QHash<int, DelegateEntry> m_delegates;
|
QHash<int, DelegateEntry> m_delegates;
|
||||||
QHash<QQuickItem*, int> m_itemToIndex;
|
QHash<QQuickItem*, int> m_itemToIndex;
|
||||||
QVector<DelegateEntry> m_dyingDelegates;
|
QVector<DelegateEntry> m_dyingDelegates;
|
||||||
|
|
||||||
bool m_componentComplete = false;
|
bool m_componentComplete = false;
|
||||||
bool m_relayoutPending = false;
|
bool m_relayoutPending = false;
|
||||||
|
|
||||||
QList<QMetaObject::Connection> m_modelConnections;
|
QList<QMetaObject::Connection> m_modelConnections;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::components
|
} // namespace ZShell::components
|
||||||
|
|||||||
@@ -194,7 +194,8 @@ void WavyLine::paintLinear(QPainter* painter) {
|
|||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
for (int x = m_lineWidth / 2; x <= drawEnd; ++x) {
|
for (int x = m_lineWidth / 2; x <= drawEnd; ++x) {
|
||||||
const auto theta = m_frequency * 2 * M_PI * (x + m_startX) / len + phase;
|
const auto theta =
|
||||||
|
m_frequency * 2 * M_PI * (x + m_startX) / len + phase;
|
||||||
const auto waveY = centerY + amplitude * qSin(theta);
|
const auto waveY = centerY + amplitude * qSin(theta);
|
||||||
if (first) {
|
if (first) {
|
||||||
path.moveTo(x, waveY);
|
path.moveTo(x, waveY);
|
||||||
@@ -215,7 +216,10 @@ void WavyLine::paintArc(QPainter* painter) {
|
|||||||
const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
|
const auto amplitude = m_lineWidth * m_amplitudeMultiplier;
|
||||||
const auto cx = width() / 2.0;
|
const auto cx = width() / 2.0;
|
||||||
const auto cy = height() / 2.0;
|
const auto cy = height() / 2.0;
|
||||||
const auto radius = m_radius > 0 ? m_radius : (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0;
|
const auto radius =
|
||||||
|
m_radius > 0
|
||||||
|
? m_radius
|
||||||
|
: (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0;
|
||||||
|
|
||||||
if (radius <= 0) {
|
if (radius <= 0) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -6,102 +6,116 @@
|
|||||||
namespace ZShell::controls {
|
namespace ZShell::controls {
|
||||||
|
|
||||||
class WavyLine : public QQuickPaintedItem {
|
class WavyLine : public QQuickPaintedItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged FINAL)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal amplitudeMultiplier READ amplitudeMultiplier WRITE setAmplitudeMultiplier NOTIFY
|
int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged
|
||||||
amplitudeMultiplierChanged FINAL)
|
FINAL)
|
||||||
Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged FINAL)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL)
|
qreal amplitudeMultiplier READ amplitudeMultiplier WRITE
|
||||||
Q_PROPERTY(qreal fullLength READ fullLength WRITE setFullLength NOTIFY fullLengthChanged FINAL)
|
setAmplitudeMultiplier NOTIFY amplitudeMultiplierChanged FINAL)
|
||||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY waveProgressChanged FINAL)
|
int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged
|
||||||
Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL)
|
FINAL)
|
||||||
Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY fullAngleChanged FINAL)
|
qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL)
|
||||||
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL)
|
qreal fullLength READ fullLength WRITE setFullLength NOTIFY
|
||||||
|
fullLengthChanged FINAL)
|
||||||
|
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY
|
||||||
|
waveProgressChanged FINAL)
|
||||||
|
Q_PROPERTY(
|
||||||
|
PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged
|
||||||
|
FINAL)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal startAngle READ startAngle WRITE setStartAngle NOTIFY
|
||||||
|
startAngleChanged FINAL)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY
|
||||||
|
fullAngleChanged FINAL)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL)
|
||||||
|
Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum PathType {
|
enum PathType { Linear, Arc };
|
||||||
Linear,
|
Q_ENUM(PathType)
|
||||||
Arc
|
|
||||||
};
|
|
||||||
Q_ENUM(PathType)
|
|
||||||
|
|
||||||
explicit WavyLine(QQuickItem* parent = nullptr);
|
explicit WavyLine(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] int lineWidth() const;
|
[[nodiscard]] int lineWidth() const;
|
||||||
void setLineWidth(int lineWidth);
|
void setLineWidth(int lineWidth);
|
||||||
|
|
||||||
[[nodiscard]] qreal amplitudeMultiplier() const;
|
[[nodiscard]] qreal amplitudeMultiplier() const;
|
||||||
void setAmplitudeMultiplier(qreal amplitudeMultiplier);
|
void setAmplitudeMultiplier(qreal amplitudeMultiplier);
|
||||||
|
|
||||||
[[nodiscard]] int frequency() const;
|
[[nodiscard]] int frequency() const;
|
||||||
void setFrequency(int frequency);
|
void setFrequency(int frequency);
|
||||||
|
|
||||||
[[nodiscard]] qreal startX() const;
|
[[nodiscard]] qreal startX() const;
|
||||||
void setStartX(qreal startX);
|
void setStartX(qreal startX);
|
||||||
|
|
||||||
[[nodiscard]] qreal fullLength() const;
|
[[nodiscard]] qreal fullLength() const;
|
||||||
void setFullLength(qreal fullLength);
|
void setFullLength(qreal fullLength);
|
||||||
|
|
||||||
[[nodiscard]] QColor color() const;
|
[[nodiscard]] QColor color() const;
|
||||||
void setColor(const QColor& color);
|
void setColor(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] qreal waveProgress() const;
|
[[nodiscard]] qreal waveProgress() const;
|
||||||
void setWaveProgress(qreal progress);
|
void setWaveProgress(qreal progress);
|
||||||
|
|
||||||
[[nodiscard]] PathType pathType() const;
|
[[nodiscard]] PathType pathType() const;
|
||||||
void setPathType(PathType pathType);
|
void setPathType(PathType pathType);
|
||||||
|
|
||||||
[[nodiscard]] qreal startAngle() const;
|
[[nodiscard]] qreal startAngle() const;
|
||||||
void setStartAngle(qreal startAngle);
|
void setStartAngle(qreal startAngle);
|
||||||
|
|
||||||
[[nodiscard]] qreal fullAngle() const;
|
[[nodiscard]] qreal fullAngle() const;
|
||||||
void setFullAngle(qreal fullAngle);
|
void setFullAngle(qreal fullAngle);
|
||||||
|
|
||||||
[[nodiscard]] qreal radius() const;
|
[[nodiscard]] qreal radius() const;
|
||||||
void setRadius(qreal radius);
|
void setRadius(qreal radius);
|
||||||
|
|
||||||
[[nodiscard]] qreal value() const;
|
[[nodiscard]] qreal value() const;
|
||||||
void setValue(qreal value);
|
void setValue(qreal value);
|
||||||
|
|
||||||
void paint(QPainter* painter) override;
|
void paint(QPainter* painter) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void lineWidthChanged();
|
void lineWidthChanged();
|
||||||
void amplitudeMultiplierChanged();
|
void amplitudeMultiplierChanged();
|
||||||
void frequencyChanged();
|
void frequencyChanged();
|
||||||
void startXChanged();
|
void startXChanged();
|
||||||
void fullLengthChanged();
|
void fullLengthChanged();
|
||||||
void colorChanged();
|
void colorChanged();
|
||||||
void waveProgressChanged();
|
void waveProgressChanged();
|
||||||
void pathTypeChanged();
|
void pathTypeChanged();
|
||||||
void startAngleChanged();
|
void startAngleChanged();
|
||||||
void fullAngleChanged();
|
void fullAngleChanged();
|
||||||
void radiusChanged();
|
void radiusChanged();
|
||||||
void valueChanged();
|
void valueChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void paintLinear(QPainter* painter);
|
void paintLinear(QPainter* painter);
|
||||||
void paintArc(QPainter* painter);
|
void paintArc(QPainter* painter);
|
||||||
|
|
||||||
int m_lineWidth;
|
int m_lineWidth;
|
||||||
qreal m_amplitudeMultiplier;
|
qreal m_amplitudeMultiplier;
|
||||||
int m_frequency;
|
int m_frequency;
|
||||||
qreal m_startX;
|
qreal m_startX;
|
||||||
qreal m_fullLength;
|
qreal m_fullLength;
|
||||||
QColor m_color;
|
QColor m_color;
|
||||||
qreal m_waveProgress;
|
qreal m_waveProgress;
|
||||||
PathType m_pathType;
|
PathType m_pathType;
|
||||||
qreal m_startAngle;
|
qreal m_startAngle;
|
||||||
qreal m_fullAngle;
|
qreal m_fullAngle;
|
||||||
qreal m_radius;
|
qreal m_radius;
|
||||||
qreal m_value;
|
qreal m_value;
|
||||||
qreal m_startAngleRad;
|
qreal m_startAngleRad;
|
||||||
qreal m_fullAngleRad;
|
qreal m_fullAngleRad;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::controls
|
} // namespace ZShell::controls
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
ArcGauge::ArcGauge(QQuickItem* parent)
|
ArcGauge::ArcGauge(QQuickItem* parent) : QQuickPaintedItem(parent) {
|
||||||
: QQuickPaintedItem(parent) {
|
|
||||||
setAntialiasing(true);
|
setAntialiasing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +35,8 @@ void ArcGauge::paint(QPainter* painter) {
|
|||||||
|
|
||||||
// Draw value arc
|
// Draw value arc
|
||||||
if (m_percentage > 0.0) {
|
if (m_percentage > 0.0) {
|
||||||
const int valueSweep16 = qRound(static_cast<qreal>(sweepAngle16) * m_percentage);
|
const int valueSweep16 =
|
||||||
|
qRound(static_cast<qreal>(sweepAngle16) * m_percentage);
|
||||||
QPen valuePen(m_accentColor, m_lineWidth);
|
QPen valuePen(m_accentColor, m_lineWidth);
|
||||||
valuePen.setCapStyle(Qt::RoundCap);
|
valuePen.setCapStyle(Qt::RoundCap);
|
||||||
painter->setPen(valuePen);
|
painter->setPen(valuePen);
|
||||||
@@ -49,8 +49,7 @@ qreal ArcGauge::percentage() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArcGauge::setPercentage(qreal percentage) {
|
void ArcGauge::setPercentage(qreal percentage) {
|
||||||
if (qFuzzyCompare(m_percentage, percentage))
|
if (qFuzzyCompare(m_percentage, percentage)) return;
|
||||||
return;
|
|
||||||
m_percentage = percentage;
|
m_percentage = percentage;
|
||||||
emit percentageChanged();
|
emit percentageChanged();
|
||||||
update();
|
update();
|
||||||
@@ -61,8 +60,7 @@ QColor ArcGauge::accentColor() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArcGauge::setAccentColor(const QColor& color) {
|
void ArcGauge::setAccentColor(const QColor& color) {
|
||||||
if (m_accentColor == color)
|
if (m_accentColor == color) return;
|
||||||
return;
|
|
||||||
m_accentColor = color;
|
m_accentColor = color;
|
||||||
emit accentColorChanged();
|
emit accentColorChanged();
|
||||||
update();
|
update();
|
||||||
@@ -73,8 +71,7 @@ QColor ArcGauge::trackColor() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArcGauge::setTrackColor(const QColor& color) {
|
void ArcGauge::setTrackColor(const QColor& color) {
|
||||||
if (m_trackColor == color)
|
if (m_trackColor == color) return;
|
||||||
return;
|
|
||||||
m_trackColor = color;
|
m_trackColor = color;
|
||||||
emit trackColorChanged();
|
emit trackColorChanged();
|
||||||
update();
|
update();
|
||||||
@@ -85,8 +82,7 @@ qreal ArcGauge::startAngle() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArcGauge::setStartAngle(qreal angle) {
|
void ArcGauge::setStartAngle(qreal angle) {
|
||||||
if (qFuzzyCompare(m_startAngle, angle))
|
if (qFuzzyCompare(m_startAngle, angle)) return;
|
||||||
return;
|
|
||||||
m_startAngle = angle;
|
m_startAngle = angle;
|
||||||
emit startAngleChanged();
|
emit startAngleChanged();
|
||||||
update();
|
update();
|
||||||
@@ -97,8 +93,7 @@ qreal ArcGauge::sweepAngle() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArcGauge::setSweepAngle(qreal angle) {
|
void ArcGauge::setSweepAngle(qreal angle) {
|
||||||
if (qFuzzyCompare(m_sweepAngle, angle))
|
if (qFuzzyCompare(m_sweepAngle, angle)) return;
|
||||||
return;
|
|
||||||
m_sweepAngle = angle;
|
m_sweepAngle = angle;
|
||||||
emit sweepAngleChanged();
|
emit sweepAngleChanged();
|
||||||
update();
|
update();
|
||||||
@@ -109,8 +104,7 @@ qreal ArcGauge::lineWidth() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArcGauge::setLineWidth(qreal width) {
|
void ArcGauge::setLineWidth(qreal width) {
|
||||||
if (qFuzzyCompare(m_lineWidth, width))
|
if (qFuzzyCompare(m_lineWidth, width)) return;
|
||||||
return;
|
|
||||||
m_lineWidth = width;
|
m_lineWidth = width;
|
||||||
emit lineWidthChanged();
|
emit lineWidthChanged();
|
||||||
update();
|
update();
|
||||||
|
|||||||
@@ -8,54 +8,66 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class ArcGauge : public QQuickPaintedItem {
|
class ArcGauge : public QQuickPaintedItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(qreal percentage READ percentage WRITE setPercentage NOTIFY percentageChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged)
|
qreal percentage READ percentage WRITE setPercentage NOTIFY
|
||||||
Q_PROPERTY(QColor trackColor READ trackColor WRITE setTrackColor NOTIFY trackColorChanged)
|
percentageChanged)
|
||||||
Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY sweepAngleChanged)
|
QColor accentColor READ accentColor WRITE setAccentColor NOTIFY
|
||||||
Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged)
|
accentColorChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QColor trackColor READ trackColor WRITE setTrackColor NOTIFY
|
||||||
|
trackColorChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal startAngle READ startAngle WRITE setStartAngle NOTIFY
|
||||||
|
startAngleChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY
|
||||||
|
sweepAngleChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
|
||||||
|
lineWidthChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ArcGauge(QQuickItem* parent = nullptr);
|
explicit ArcGauge(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
void paint(QPainter* painter) override;
|
void paint(QPainter* painter) override;
|
||||||
|
|
||||||
[[nodiscard]] qreal percentage() const;
|
[[nodiscard]] qreal percentage() const;
|
||||||
void setPercentage(qreal percentage);
|
void setPercentage(qreal percentage);
|
||||||
|
|
||||||
[[nodiscard]] QColor accentColor() const;
|
[[nodiscard]] QColor accentColor() const;
|
||||||
void setAccentColor(const QColor& color);
|
void setAccentColor(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] QColor trackColor() const;
|
[[nodiscard]] QColor trackColor() const;
|
||||||
void setTrackColor(const QColor& color);
|
void setTrackColor(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] qreal startAngle() const;
|
[[nodiscard]] qreal startAngle() const;
|
||||||
void setStartAngle(qreal angle);
|
void setStartAngle(qreal angle);
|
||||||
|
|
||||||
[[nodiscard]] qreal sweepAngle() const;
|
[[nodiscard]] qreal sweepAngle() const;
|
||||||
void setSweepAngle(qreal angle);
|
void setSweepAngle(qreal angle);
|
||||||
|
|
||||||
[[nodiscard]] qreal lineWidth() const;
|
[[nodiscard]] qreal lineWidth() const;
|
||||||
void setLineWidth(qreal width);
|
void setLineWidth(qreal width);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void percentageChanged();
|
void percentageChanged();
|
||||||
void accentColorChanged();
|
void accentColorChanged();
|
||||||
void trackColorChanged();
|
void trackColorChanged();
|
||||||
void startAngleChanged();
|
void startAngleChanged();
|
||||||
void sweepAngleChanged();
|
void sweepAngleChanged();
|
||||||
void lineWidthChanged();
|
void lineWidthChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qreal m_percentage = 0.0;
|
qreal m_percentage = 0.0;
|
||||||
QColor m_accentColor;
|
QColor m_accentColor;
|
||||||
QColor m_trackColor;
|
QColor m_trackColor;
|
||||||
qreal m_startAngle = 0.75 * M_PI;
|
qreal m_startAngle = 0.75 * M_PI;
|
||||||
qreal m_sweepAngle = 1.5 * M_PI;
|
qreal m_sweepAngle = 1.5 * M_PI;
|
||||||
qreal m_lineWidth = 10.0;
|
qreal m_lineWidth = 10.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::Internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -12,212 +12,232 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
qreal CachingImageManager::effectiveScale() const {
|
qreal CachingImageManager::effectiveScale() const {
|
||||||
if (m_item && m_item->window()) {
|
if (m_item && m_item->window()) {
|
||||||
return m_item->window()->devicePixelRatio();
|
return m_item->window()->devicePixelRatio();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize CachingImageManager::effectiveSize() const {
|
QSize CachingImageManager::effectiveSize() const {
|
||||||
if (!m_item) {
|
if (!m_item) {
|
||||||
return QSize();
|
return QSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
const qreal scale = effectiveScale();
|
const qreal scale = effectiveScale();
|
||||||
const QSize size = QSizeF(m_item->width() * scale, m_item->height() * scale).toSize();
|
const QSize size =
|
||||||
m_item->setProperty("sourceSize", size);
|
QSizeF(m_item->width() * scale, m_item->height() * scale).toSize();
|
||||||
return size;
|
m_item->setProperty("sourceSize", size);
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickItem* CachingImageManager::item() const {
|
QQuickItem* CachingImageManager::item() const {
|
||||||
return m_item;
|
return m_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CachingImageManager::setItem(QQuickItem* item) {
|
void CachingImageManager::setItem(QQuickItem* item) {
|
||||||
if (m_item == item) {
|
if (m_item == item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_widthConn) {
|
if (m_widthConn) {
|
||||||
disconnect(m_widthConn);
|
disconnect(m_widthConn);
|
||||||
}
|
}
|
||||||
if (m_heightConn) {
|
if (m_heightConn) {
|
||||||
disconnect(m_heightConn);
|
disconnect(m_heightConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_item = item;
|
m_item = item;
|
||||||
emit itemChanged();
|
emit itemChanged();
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() {
|
m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() {
|
||||||
updateSource();
|
updateSource();
|
||||||
});
|
});
|
||||||
m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() {
|
m_heightConn =
|
||||||
updateSource();
|
connect(item, &QQuickItem::heightChanged, this, [this]() {
|
||||||
});
|
updateSource();
|
||||||
updateSource();
|
});
|
||||||
}
|
updateSource();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl CachingImageManager::cacheDir() const {
|
QUrl CachingImageManager::cacheDir() const {
|
||||||
return m_cacheDir;
|
return m_cacheDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CachingImageManager::setCacheDir(const QUrl& cacheDir) {
|
void CachingImageManager::setCacheDir(const QUrl& cacheDir) {
|
||||||
if (m_cacheDir == cacheDir) {
|
if (m_cacheDir == cacheDir) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cacheDir = cacheDir;
|
m_cacheDir = cacheDir;
|
||||||
if (!m_cacheDir.path().endsWith("/")) {
|
if (!m_cacheDir.path().endsWith("/")) {
|
||||||
m_cacheDir.setPath(m_cacheDir.path() + "/");
|
m_cacheDir.setPath(m_cacheDir.path() + "/");
|
||||||
}
|
}
|
||||||
emit cacheDirChanged();
|
emit cacheDirChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CachingImageManager::path() const {
|
QString CachingImageManager::path() const {
|
||||||
return m_path;
|
return m_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CachingImageManager::setPath(const QString& path) {
|
void CachingImageManager::setPath(const QString& path) {
|
||||||
if (m_path == path) {
|
if (m_path == path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_path = path;
|
m_path = path;
|
||||||
emit pathChanged();
|
emit pathChanged();
|
||||||
|
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
updateSource(path);
|
updateSource(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CachingImageManager::updateSource() {
|
void CachingImageManager::updateSource() {
|
||||||
updateSource(m_path);
|
updateSource(m_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CachingImageManager::updateSource(const QString& path) {
|
void CachingImageManager::updateSource(const QString& path) {
|
||||||
if (path.isEmpty() || path == m_shaPath) {
|
if (path.isEmpty() || path == m_shaPath) {
|
||||||
// Path is empty or already calculating sha for path
|
// Path is empty or already calculating sha for path
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_shaPath = path;
|
m_shaPath = path;
|
||||||
|
|
||||||
const auto future = QtConcurrent::run(&CachingImageManager::sha256sum, path);
|
const auto future =
|
||||||
|
QtConcurrent::run(&CachingImageManager::sha256sum, path);
|
||||||
|
|
||||||
const auto watcher = new QFutureWatcher<QString>(this);
|
const auto watcher = new QFutureWatcher<QString>(this);
|
||||||
|
|
||||||
connect(watcher, &QFutureWatcher<QString>::finished, this, [watcher, path, this]() {
|
connect(
|
||||||
if (m_path != path) {
|
watcher,
|
||||||
// Object is destroyed or path has changed, ignore
|
&QFutureWatcher<QString>::finished,
|
||||||
watcher->deleteLater();
|
this,
|
||||||
return;
|
[watcher, path, this]() {
|
||||||
}
|
if (m_path != path) {
|
||||||
|
// Object is destroyed or path has changed, ignore
|
||||||
|
watcher->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const QSize size = effectiveSize();
|
const QSize size = effectiveSize();
|
||||||
|
|
||||||
if (!m_item || !size.width() || !size.height()) {
|
if (!m_item || !size.width() || !size.height()) {
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString fillMode = m_item->property("fillMode").toString();
|
const QString fillMode = m_item->property("fillMode").toString();
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const QString filename = QString("%1@%2x%3-%4.png")
|
const QString filename = QString("%1@%2x%3-%4.png")
|
||||||
.arg(watcher->result()).arg(size.width()).arg(size.height())
|
.arg(watcher->result()).arg(size.width()).arg(size.height())
|
||||||
.arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch");
|
.arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch");
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
const QUrl cache = m_cacheDir.resolved(QUrl(filename));
|
const QUrl cache = m_cacheDir.resolved(QUrl(filename));
|
||||||
if (m_cachePath == cache) {
|
if (m_cachePath == cache) {
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cachePath = cache;
|
m_cachePath = cache;
|
||||||
emit cachePathChanged();
|
emit cachePathChanged();
|
||||||
|
|
||||||
if (!cache.isLocalFile()) {
|
if (!cache.isLocalFile()) {
|
||||||
qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file";
|
qWarning() << "CachingImageManager::updateSource: cachePath"
|
||||||
watcher->deleteLater();
|
<< cache << "is not a local file";
|
||||||
return;
|
watcher->deleteLater();
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const QImageReader reader(cache.toLocalFile());
|
const QImageReader reader(cache.toLocalFile());
|
||||||
if (reader.canRead()) {
|
if (reader.canRead()) {
|
||||||
m_item->setProperty("source", cache);
|
m_item->setProperty("source", cache);
|
||||||
} else {
|
} else {
|
||||||
m_item->setProperty("source", QUrl::fromLocalFile(path));
|
m_item->setProperty("source", QUrl::fromLocalFile(path));
|
||||||
createCache(path, cache.toLocalFile(), fillMode, size);
|
createCache(path, cache.toLocalFile(), fillMode, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear current running sha if same
|
// Clear current running sha if same
|
||||||
if (m_shaPath == path) {
|
if (m_shaPath == path) {
|
||||||
m_shaPath = QString();
|
m_shaPath = QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl CachingImageManager::cachePath() const {
|
QUrl CachingImageManager::cachePath() const {
|
||||||
return m_cachePath;
|
return m_cachePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CachingImageManager::createCache(
|
void CachingImageManager::createCache(
|
||||||
const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const {
|
const QString& path,
|
||||||
QThreadPool::globalInstance()->start([path, cache, fillMode, size] {
|
const QString& cache,
|
||||||
QImage image(path);
|
const QString& fillMode,
|
||||||
|
const QSize& size) const {
|
||||||
|
QThreadPool::globalInstance()->start([path, cache, fillMode, size] {
|
||||||
|
QImage image(path);
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
qWarning() << "CachingImageManager::createCache: failed to read" << path;
|
qWarning() << "CachingImageManager::createCache: failed to read"
|
||||||
return;
|
<< path;
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
image.convertTo(QImage::Format_ARGB32);
|
image.convertTo(QImage::Format_ARGB32);
|
||||||
|
|
||||||
if (fillMode == "PreserveAspectCrop") {
|
if (fillMode == "PreserveAspectCrop") {
|
||||||
image = image.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
image = image.scaled(
|
||||||
} else if (fillMode == "PreserveAspectFit") {
|
size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
||||||
image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
} else if (fillMode == "PreserveAspectFit") {
|
||||||
} else {
|
image = image.scaled(
|
||||||
image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
}
|
} else {
|
||||||
|
image = image.scaled(
|
||||||
|
size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
if (fillMode == "PreserveAspectCrop" || fillMode == "PreserveAspectFit") {
|
if (fillMode == "PreserveAspectCrop" ||
|
||||||
QImage canvas(size, QImage::Format_ARGB32);
|
fillMode == "PreserveAspectFit") {
|
||||||
canvas.fill(Qt::transparent);
|
QImage canvas(size, QImage::Format_ARGB32);
|
||||||
|
canvas.fill(Qt::transparent);
|
||||||
|
|
||||||
QPainter painter(&canvas);
|
QPainter painter(&canvas);
|
||||||
painter.drawImage((size.width() - image.width()) / 2, (size.height() - image.height()) / 2, image);
|
painter.drawImage(
|
||||||
painter.end();
|
(size.width() - image.width()) / 2,
|
||||||
|
(size.height() - image.height()) / 2,
|
||||||
|
image);
|
||||||
|
painter.end();
|
||||||
|
|
||||||
image = canvas;
|
image = canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString parent = QFileInfo(cache).absolutePath();
|
const QString parent = QFileInfo(cache).absolutePath();
|
||||||
if (!QDir().mkpath(parent) || !image.save(cache)) {
|
if (!QDir().mkpath(parent) || !image.save(cache)) {
|
||||||
qWarning() << "CachingImageManager::createCache: failed to save to" << cache;
|
qWarning() << "CachingImageManager::createCache: failed to save to"
|
||||||
}
|
<< cache;
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CachingImageManager::sha256sum(const QString& path) {
|
QString CachingImageManager::sha256sum(const QString& path) {
|
||||||
QFile file(path);
|
QFile file(path);
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
qWarning() << "CachingImageManager::sha256sum: failed to open" << path;
|
qWarning() << "CachingImageManager::sha256sum: failed to open" << path;
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha256);
|
QCryptographicHash hash(QCryptographicHash::Sha256);
|
||||||
hash.addData(&file);
|
hash.addData(&file);
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
return hash.result().toHex();
|
return hash.result().toHex();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -7,60 +7,65 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class CachingImageManager : public QObject {
|
class CachingImageManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED)
|
QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged
|
||||||
|
REQUIRED)
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
|
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CachingImageManager(QObject* parent = nullptr)
|
explicit CachingImageManager(QObject* parent = nullptr)
|
||||||
: QObject(parent)
|
: QObject(parent), m_item(nullptr) {}
|
||||||
, m_item(nullptr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QQuickItem* item() const;
|
[[nodiscard]] QQuickItem* item() const;
|
||||||
void setItem(QQuickItem* item);
|
void setItem(QQuickItem* item);
|
||||||
|
|
||||||
[[nodiscard]] QUrl cacheDir() const;
|
[[nodiscard]] QUrl cacheDir() const;
|
||||||
void setCacheDir(const QUrl& cacheDir);
|
void setCacheDir(const QUrl& cacheDir);
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
void setPath(const QString& path);
|
void setPath(const QString& path);
|
||||||
|
|
||||||
[[nodiscard]] QUrl cachePath() const;
|
[[nodiscard]] QUrl cachePath() const;
|
||||||
|
|
||||||
Q_INVOKABLE void updateSource();
|
Q_INVOKABLE void updateSource();
|
||||||
Q_INVOKABLE void updateSource(const QString& path);
|
Q_INVOKABLE void updateSource(const QString& path);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void itemChanged();
|
void itemChanged();
|
||||||
void cacheDirChanged();
|
void cacheDirChanged();
|
||||||
|
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void cachePathChanged();
|
void cachePathChanged();
|
||||||
void usingCacheChanged();
|
void usingCacheChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_shaPath;
|
QString m_shaPath;
|
||||||
|
|
||||||
QQuickItem* m_item;
|
QQuickItem* m_item;
|
||||||
QUrl m_cacheDir;
|
QUrl m_cacheDir;
|
||||||
|
|
||||||
QString m_path;
|
QString m_path;
|
||||||
QUrl m_cachePath;
|
QUrl m_cachePath;
|
||||||
|
|
||||||
QMetaObject::Connection m_widthConn;
|
QMetaObject::Connection m_widthConn;
|
||||||
QMetaObject::Connection m_heightConn;
|
QMetaObject::Connection m_heightConn;
|
||||||
|
|
||||||
[[nodiscard]] qreal effectiveScale() const;
|
[[nodiscard]] qreal effectiveScale() const;
|
||||||
[[nodiscard]] QSize effectiveSize() const;
|
[[nodiscard]] QSize effectiveSize() const;
|
||||||
|
|
||||||
void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const;
|
void createCache(
|
||||||
[[nodiscard]] static QString sha256sum(const QString& path);
|
const QString& path,
|
||||||
|
const QString& cache,
|
||||||
|
const QString& fillMode,
|
||||||
|
const QSize& size) const;
|
||||||
|
[[nodiscard]] static QString sha256sum(const QString& path);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -4,19 +4,15 @@
|
|||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
CircularBuffer::CircularBuffer(QObject* parent)
|
CircularBuffer::CircularBuffer(QObject* parent) : QObject(parent) {}
|
||||||
: QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
int CircularBuffer::capacity() const {
|
int CircularBuffer::capacity() const {
|
||||||
return m_capacity;
|
return m_capacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CircularBuffer::setCapacity(int capacity) {
|
void CircularBuffer::setCapacity(int capacity) {
|
||||||
if (capacity < 0)
|
if (capacity < 0) capacity = 0;
|
||||||
capacity = 0;
|
if (m_capacity == capacity) return;
|
||||||
if (m_capacity == capacity)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const auto old = values();
|
const auto old = values();
|
||||||
|
|
||||||
@@ -52,8 +48,7 @@ QList<qreal> CircularBuffer::values() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qreal CircularBuffer::maximum() const {
|
qreal CircularBuffer::maximum() const {
|
||||||
if (m_count == 0)
|
if (m_count == 0) return 0.0;
|
||||||
return 0.0;
|
|
||||||
|
|
||||||
qreal maxVal = at(0);
|
qreal maxVal = at(0);
|
||||||
for (int i = 1; i < m_count; ++i)
|
for (int i = 1; i < m_count; ++i)
|
||||||
@@ -62,8 +57,7 @@ qreal CircularBuffer::maximum() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CircularBuffer::push(qreal value) {
|
void CircularBuffer::push(qreal value) {
|
||||||
if (m_capacity <= 0)
|
if (m_capacity <= 0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_data[m_head] = value;
|
m_data[m_head] = value;
|
||||||
m_head = (m_head + 1) % m_capacity;
|
m_head = (m_head + 1) % m_capacity;
|
||||||
@@ -75,8 +69,7 @@ void CircularBuffer::push(qreal value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CircularBuffer::clear() {
|
void CircularBuffer::clear() {
|
||||||
if (m_count == 0)
|
if (m_count == 0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_head = 0;
|
m_head = 0;
|
||||||
m_count = 0;
|
m_count = 0;
|
||||||
@@ -85,10 +78,10 @@ void CircularBuffer::clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qreal CircularBuffer::at(int index) const {
|
qreal CircularBuffer::at(int index) const {
|
||||||
if (index < 0 || index >= m_count)
|
if (index < 0 || index >= m_count) return 0.0;
|
||||||
return 0.0;
|
|
||||||
|
|
||||||
const int actualIndex = (m_head - m_count + index + m_capacity) % m_capacity;
|
const int actualIndex =
|
||||||
|
(m_head - m_count + index + m_capacity) % m_capacity;
|
||||||
return m_data[actualIndex];
|
return m_data[actualIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,38 +7,39 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class CircularBuffer : public QObject {
|
class CircularBuffer : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged)
|
||||||
Q_PROPERTY(QList<qreal> values READ values NOTIFY valuesChanged)
|
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
||||||
Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged)
|
Q_PROPERTY(QList<qreal> values READ values NOTIFY valuesChanged)
|
||||||
|
Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CircularBuffer(QObject* parent = nullptr);
|
explicit CircularBuffer(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] int capacity() const;
|
[[nodiscard]] int capacity() const;
|
||||||
void setCapacity(int capacity);
|
void setCapacity(int capacity);
|
||||||
|
|
||||||
[[nodiscard]] int count() const;
|
[[nodiscard]] int count() const;
|
||||||
[[nodiscard]] QList<qreal> values() const;
|
[[nodiscard]] QList<qreal> values() const;
|
||||||
[[nodiscard]] qreal maximum() const;
|
[[nodiscard]] qreal maximum() const;
|
||||||
|
|
||||||
Q_INVOKABLE void push(qreal value);
|
Q_INVOKABLE void push(qreal value);
|
||||||
Q_INVOKABLE void clear();
|
Q_INVOKABLE void clear();
|
||||||
Q_INVOKABLE [[nodiscard]] qreal at(int index) const;
|
Q_INVOKABLE [[nodiscard]] qreal at(int index) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void capacityChanged();
|
void capacityChanged();
|
||||||
void countChanged();
|
void countChanged();
|
||||||
void valuesChanged();
|
void valuesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QVector<qreal> m_data;
|
QVector<qreal> m_data;
|
||||||
int m_head = 0;
|
int m_head = 0;
|
||||||
int m_count = 0;
|
int m_count = 0;
|
||||||
int m_capacity = 0;
|
int m_capacity = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ constexpr qint32 TAIL_DEGREES_OFFSET = -20;
|
|||||||
constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250;
|
constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250;
|
||||||
constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520;
|
constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520;
|
||||||
|
|
||||||
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_EXPAND_IN_MS = { 0, 1350, 2700, 4050 };
|
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_EXPAND_IN_MS =
|
||||||
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717 };
|
{0, 1350, 2700, 4050};
|
||||||
|
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_COLLAPSE_IN_MS = {
|
||||||
|
667, 2017, 3367, 4717};
|
||||||
|
|
||||||
} // namespace advance
|
} // namespace advance
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ constexpr qint32 TOTAL_DURATION_IN_MS = 6000;
|
|||||||
constexpr qint32 DURATION_SPIN_IN_MS = 500;
|
constexpr qint32 DURATION_SPIN_IN_MS = 500;
|
||||||
constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000;
|
constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000;
|
||||||
constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000;
|
constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000;
|
||||||
constexpr std::array DELAY_SPINS_IN_MS = { 0, 1500, 3000, 4500 };
|
constexpr std::array DELAY_SPINS_IN_MS = {0, 1500, 3000, 4500};
|
||||||
constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0;
|
constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0;
|
||||||
constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000;
|
constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000;
|
||||||
constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500;
|
constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500;
|
||||||
@@ -38,7 +40,7 @@ constexpr qint32 CONSTANT_ROTATION_DEGREES = 1080;
|
|||||||
// Despite of the constant rotation, there are also 5 extra rotations the entire animation. The
|
// Despite of the constant rotation, there are also 5 extra rotations the entire animation. The
|
||||||
// total degrees that each extra rotation goes by.
|
// total degrees that each extra rotation goes by.
|
||||||
constexpr qint32 SPIN_ROTATION_DEGREES = 90;
|
constexpr qint32 SPIN_ROTATION_DEGREES = 90;
|
||||||
constexpr std::array<qreal, 2> END_FRACTION_RANGE = { 0.10, 0.87 };
|
constexpr std::array<qreal, 2> END_FRACTION_RANGE = {0.10, 0.87};
|
||||||
|
|
||||||
} // namespace retreat
|
} // namespace retreat
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ CircularIndicatorManager::CircularIndicatorManager(QObject* parent)
|
|||||||
, m_rotation(0)
|
, m_rotation(0)
|
||||||
, m_completeEndProgress(0) {
|
, m_completeEndProgress(0) {
|
||||||
// Fast out slow in
|
// Fast out slow in
|
||||||
m_curve.addCubicBezierSegment({ 0.4, 0.0 }, { 0.2, 1.0 }, { 1.0, 1.0 });
|
m_curve.addCubicBezierSegment({0.4, 0.0}, {0.2, 1.0}, {1.0, 1.0});
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal CircularIndicatorManager::startFraction() const {
|
qreal CircularIndicatorManager::startFraction() const {
|
||||||
@@ -100,11 +102,13 @@ qreal CircularIndicatorManager::completeEndDuration() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CircularIndicatorManager::IndeterminateAnimationType CircularIndicatorManager::indeterminateAnimationType() const {
|
CircularIndicatorManager::IndeterminateAnimationType
|
||||||
|
CircularIndicatorManager::indeterminateAnimationType() const {
|
||||||
return m_type;
|
return m_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CircularIndicatorManager::setIndeterminateAnimationType(IndeterminateAnimationType t) {
|
void CircularIndicatorManager::setIndeterminateAnimationType(
|
||||||
|
IndeterminateAnimationType t) {
|
||||||
if (m_type != t) {
|
if (m_type != t) {
|
||||||
m_type = t;
|
m_type = t;
|
||||||
emit indeterminateAnimationTypeChanged();
|
emit indeterminateAnimationTypeChanged();
|
||||||
@@ -150,24 +154,27 @@ void CircularIndicatorManager::updateRetreat(qreal progress) {
|
|||||||
// Extra rotation for the faster spinning.
|
// Extra rotation for the faster spinning.
|
||||||
qreal spinRotation = 0;
|
qreal spinRotation = 0;
|
||||||
for (const int spinDelay : DELAY_SPINS_IN_MS) {
|
for (const int spinDelay : DELAY_SPINS_IN_MS) {
|
||||||
spinRotation += m_curve.valueForProgress(getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) *
|
spinRotation +=
|
||||||
SPIN_ROTATION_DEGREES;
|
m_curve.valueForProgress(
|
||||||
|
getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) *
|
||||||
|
SPIN_ROTATION_DEGREES;
|
||||||
}
|
}
|
||||||
m_rotation = constantRotation + spinRotation;
|
m_rotation = constantRotation + spinRotation;
|
||||||
emit rotationChanged();
|
emit rotationChanged();
|
||||||
|
|
||||||
// Grow active indicator.
|
// Grow active indicator.
|
||||||
qreal fraction =
|
qreal fraction = m_curve.valueForProgress(getFractionInRange(
|
||||||
m_curve.valueForProgress(getFractionInRange(playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS));
|
playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS));
|
||||||
fraction -=
|
fraction -= m_curve.valueForProgress(getFractionInRange(
|
||||||
m_curve.valueForProgress(getFractionInRange(playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS));
|
playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS));
|
||||||
|
|
||||||
if (!qFuzzyIsNull(m_startFraction)) {
|
if (!qFuzzyIsNull(m_startFraction)) {
|
||||||
m_startFraction = 0.0;
|
m_startFraction = 0.0;
|
||||||
emit startFractionChanged();
|
emit startFractionChanged();
|
||||||
}
|
}
|
||||||
const auto oldEndFrac = m_endFraction;
|
const auto oldEndFrac = m_endFraction;
|
||||||
m_endFraction = std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction);
|
m_endFraction =
|
||||||
|
std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction);
|
||||||
|
|
||||||
// Completing animation.
|
// Completing animation.
|
||||||
if (m_completeEndProgress > 0) {
|
if (m_completeEndProgress > 0) {
|
||||||
@@ -184,22 +191,32 @@ void CircularIndicatorManager::updateAdvance(qreal progress) {
|
|||||||
const auto playtime = progress * TOTAL_DURATION_IN_MS;
|
const auto playtime = progress * TOTAL_DURATION_IN_MS;
|
||||||
|
|
||||||
// Adds constant rotation to segment positions.
|
// Adds constant rotation to segment positions.
|
||||||
m_startFraction = CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET;
|
m_startFraction =
|
||||||
|
CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET;
|
||||||
m_endFraction = CONSTANT_ROTATION_DEGREES * progress;
|
m_endFraction = CONSTANT_ROTATION_DEGREES * progress;
|
||||||
|
|
||||||
// Adds cycle specific rotation to segment positions.
|
// Adds cycle specific rotation to segment positions.
|
||||||
for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) {
|
for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) {
|
||||||
// While expanding.
|
// While expanding.
|
||||||
qreal fraction = getFractionInRange(playtime, DELAY_TO_EXPAND_IN_MS[cycleIndex], DURATION_TO_EXPAND_IN_MS);
|
qreal fraction = getFractionInRange(
|
||||||
m_endFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
|
playtime,
|
||||||
|
DELAY_TO_EXPAND_IN_MS[cycleIndex],
|
||||||
|
DURATION_TO_EXPAND_IN_MS);
|
||||||
|
m_endFraction +=
|
||||||
|
m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
|
||||||
|
|
||||||
// While collapsing.
|
// While collapsing.
|
||||||
fraction = getFractionInRange(playtime, DELAY_TO_COLLAPSE_IN_MS[cycleIndex], DURATION_TO_COLLAPSE_IN_MS);
|
fraction = getFractionInRange(
|
||||||
m_startFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
|
playtime,
|
||||||
|
DELAY_TO_COLLAPSE_IN_MS[cycleIndex],
|
||||||
|
DURATION_TO_COLLAPSE_IN_MS);
|
||||||
|
m_startFraction +=
|
||||||
|
m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes the gap between head and tail for complete end.
|
// Closes the gap between head and tail for complete end.
|
||||||
m_startFraction += (m_endFraction - m_startFraction) * m_completeEndProgress;
|
m_startFraction +=
|
||||||
|
(m_endFraction - m_startFraction) * m_completeEndProgress;
|
||||||
|
|
||||||
m_startFraction /= 360.0;
|
m_startFraction /= 360.0;
|
||||||
m_endFraction /= 360.0;
|
m_endFraction /= 360.0;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <qeasingcurve.h>
|
#include <qeasingcurve.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
@@ -8,66 +7,71 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class CircularIndicatorManager : public QObject {
|
class CircularIndicatorManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY startFractionChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged)
|
qreal startFraction READ startFraction NOTIFY startFractionChanged)
|
||||||
Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged)
|
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged)
|
||||||
Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
|
Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged)
|
||||||
Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE setCompleteEndProgress NOTIFY
|
Q_PROPERTY(
|
||||||
completeEndProgressChanged)
|
qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
|
||||||
Q_PROPERTY(qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration NOTIFY indeterminateAnimationTypeChanged)
|
qreal completeEndProgress READ completeEndProgress WRITE
|
||||||
Q_PROPERTY(IndeterminateAnimationType indeterminateAnimationType READ indeterminateAnimationType WRITE
|
setCompleteEndProgress NOTIFY completeEndProgressChanged)
|
||||||
setIndeterminateAnimationType NOTIFY indeterminateAnimationTypeChanged)
|
Q_PROPERTY(
|
||||||
|
qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal completeEndDuration READ completeEndDuration NOTIFY
|
||||||
|
indeterminateAnimationTypeChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
IndeterminateAnimationType indeterminateAnimationType READ
|
||||||
|
indeterminateAnimationType WRITE setIndeterminateAnimationType
|
||||||
|
NOTIFY indeterminateAnimationTypeChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CircularIndicatorManager(QObject* parent = nullptr);
|
explicit CircularIndicatorManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
enum IndeterminateAnimationType {
|
enum IndeterminateAnimationType { Advance = 0, Retreat };
|
||||||
Advance = 0,
|
Q_ENUM(IndeterminateAnimationType)
|
||||||
Retreat
|
|
||||||
};
|
|
||||||
Q_ENUM(IndeterminateAnimationType)
|
|
||||||
|
|
||||||
[[nodiscard]] qreal startFraction() const;
|
[[nodiscard]] qreal startFraction() const;
|
||||||
[[nodiscard]] qreal endFraction() const;
|
[[nodiscard]] qreal endFraction() const;
|
||||||
[[nodiscard]] qreal rotation() const;
|
[[nodiscard]] qreal rotation() const;
|
||||||
|
|
||||||
[[nodiscard]] qreal progress() const;
|
[[nodiscard]] qreal progress() const;
|
||||||
void setProgress(qreal progress);
|
void setProgress(qreal progress);
|
||||||
|
|
||||||
[[nodiscard]] qreal completeEndProgress() const;
|
[[nodiscard]] qreal completeEndProgress() const;
|
||||||
void setCompleteEndProgress(qreal progress);
|
void setCompleteEndProgress(qreal progress);
|
||||||
|
|
||||||
[[nodiscard]] qreal duration() const;
|
[[nodiscard]] qreal duration() const;
|
||||||
[[nodiscard]] qreal completeEndDuration() const;
|
[[nodiscard]] qreal completeEndDuration() const;
|
||||||
|
|
||||||
[[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const;
|
[[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const;
|
||||||
void setIndeterminateAnimationType(IndeterminateAnimationType t);
|
void setIndeterminateAnimationType(IndeterminateAnimationType t);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void startFractionChanged();
|
void startFractionChanged();
|
||||||
void endFractionChanged();
|
void endFractionChanged();
|
||||||
void rotationChanged();
|
void rotationChanged();
|
||||||
void progressChanged();
|
void progressChanged();
|
||||||
void completeEndProgressChanged();
|
void completeEndProgressChanged();
|
||||||
void indeterminateAnimationTypeChanged();
|
void indeterminateAnimationTypeChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IndeterminateAnimationType m_type;
|
IndeterminateAnimationType m_type;
|
||||||
QEasingCurve m_curve;
|
QEasingCurve m_curve;
|
||||||
|
|
||||||
qreal m_progress;
|
qreal m_progress;
|
||||||
qreal m_startFraction;
|
qreal m_startFraction;
|
||||||
qreal m_endFraction;
|
qreal m_endFraction;
|
||||||
qreal m_rotation;
|
qreal m_rotation;
|
||||||
qreal m_completeEndProgress;
|
qreal m_completeEndProgress;
|
||||||
|
|
||||||
void update(qreal progress);
|
void update(qreal progress);
|
||||||
void updateAdvance(qreal progress);
|
void updateAdvance(qreal progress);
|
||||||
void updateRetreat(qreal progress);
|
void updateRetreat(qreal progress);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -5,130 +5,131 @@
|
|||||||
namespace ZShell::internal::hypr {
|
namespace ZShell::internal::hypr {
|
||||||
|
|
||||||
HyprKeyboard::HyprKeyboard(QJsonObject ipcObject, QObject* parent)
|
HyprKeyboard::HyprKeyboard(QJsonObject ipcObject, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent), m_lastIpcObject(ipcObject) {}
|
||||||
, m_lastIpcObject(ipcObject) {}
|
|
||||||
|
|
||||||
QVariantHash HyprKeyboard::lastIpcObject() const {
|
QVariantHash HyprKeyboard::lastIpcObject() const {
|
||||||
return m_lastIpcObject.toVariantHash();
|
return m_lastIpcObject.toVariantHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HyprKeyboard::address() const {
|
QString HyprKeyboard::address() const {
|
||||||
return m_lastIpcObject.value("address").toString();
|
return m_lastIpcObject.value("address").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HyprKeyboard::name() const {
|
QString HyprKeyboard::name() const {
|
||||||
return m_lastIpcObject.value("name").toString();
|
return m_lastIpcObject.value("name").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HyprKeyboard::layout() const {
|
QString HyprKeyboard::layout() const {
|
||||||
return m_lastIpcObject.value("layout").toString();
|
return m_lastIpcObject.value("layout").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString HyprKeyboard::activeKeymap() const {
|
QString HyprKeyboard::activeKeymap() const {
|
||||||
return m_lastIpcObject.value("active_keymap").toString();
|
return m_lastIpcObject.value("active_keymap").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HyprKeyboard::capsLock() const {
|
bool HyprKeyboard::capsLock() const {
|
||||||
return m_lastIpcObject.value("capsLock").toBool();
|
return m_lastIpcObject.value("capsLock").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HyprKeyboard::numLock() const {
|
bool HyprKeyboard::numLock() const {
|
||||||
return m_lastIpcObject.value("numLock").toBool();
|
return m_lastIpcObject.value("numLock").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HyprKeyboard::main() const {
|
bool HyprKeyboard::main() const {
|
||||||
return m_lastIpcObject.value("main").toBool();
|
return m_lastIpcObject.value("main").toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HyprKeyboard::updateLastIpcObject(QJsonObject object) {
|
bool HyprKeyboard::updateLastIpcObject(QJsonObject object) {
|
||||||
if (m_lastIpcObject == object) {
|
if (m_lastIpcObject == object) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto last = m_lastIpcObject;
|
const auto last = m_lastIpcObject;
|
||||||
|
|
||||||
m_lastIpcObject = object;
|
m_lastIpcObject = object;
|
||||||
emit lastIpcObjectChanged();
|
emit lastIpcObjectChanged();
|
||||||
|
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
if (last.value("address") != object.value("address")) {
|
if (last.value("address") != object.value("address")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit addressChanged();
|
emit addressChanged();
|
||||||
}
|
}
|
||||||
if (last.value("name") != object.value("name")) {
|
if (last.value("name") != object.value("name")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit nameChanged();
|
emit nameChanged();
|
||||||
}
|
}
|
||||||
if (last.value("layout") != object.value("layout")) {
|
if (last.value("layout") != object.value("layout")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit layoutChanged();
|
emit layoutChanged();
|
||||||
}
|
}
|
||||||
if (last.value("active_keymap") != object.value("active_keymap")) {
|
if (last.value("active_keymap") != object.value("active_keymap")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit activeKeymapChanged();
|
emit activeKeymapChanged();
|
||||||
}
|
}
|
||||||
if (last.value("capsLock") != object.value("capsLock")) {
|
if (last.value("capsLock") != object.value("capsLock")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit capsLockChanged();
|
emit capsLockChanged();
|
||||||
}
|
}
|
||||||
if (last.value("numLock") != object.value("numLock")) {
|
if (last.value("numLock") != object.value("numLock")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit numLockChanged();
|
emit numLockChanged();
|
||||||
}
|
}
|
||||||
if (last.value("main") != object.value("main")) {
|
if (last.value("main") != object.value("main")) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
emit mainChanged();
|
emit mainChanged();
|
||||||
}
|
}
|
||||||
return dirty;
|
return dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
HyprDevices::HyprDevices(QObject* parent)
|
HyprDevices::HyprDevices(QObject* parent) : QObject(parent) {}
|
||||||
: QObject(parent) {}
|
|
||||||
|
|
||||||
QQmlListProperty<HyprKeyboard> HyprDevices::keyboards() {
|
QQmlListProperty<HyprKeyboard> HyprDevices::keyboards() {
|
||||||
return QQmlListProperty<HyprKeyboard>(this, &m_keyboards);
|
return QQmlListProperty<HyprKeyboard>(this, &m_keyboards);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HyprDevices::updateLastIpcObject(QJsonObject object) {
|
bool HyprDevices::updateLastIpcObject(QJsonObject object) {
|
||||||
const auto val = object.value("keyboards").toArray();
|
const auto val = object.value("keyboards").toArray();
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
|
|
||||||
for (auto it = m_keyboards.begin(); it != m_keyboards.end();) {
|
for (auto it = m_keyboards.begin(); it != m_keyboards.end();) {
|
||||||
auto* const keyboard = *it;
|
auto* const keyboard = *it;
|
||||||
const auto inNewValues = std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) {
|
const auto inNewValues =
|
||||||
return o.toObject().value("address").toString() == keyboard->address();
|
std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) {
|
||||||
});
|
return o.toObject().value("address").toString() ==
|
||||||
|
keyboard->address();
|
||||||
|
});
|
||||||
|
|
||||||
if (!inNewValues) {
|
if (!inNewValues) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
it = m_keyboards.erase(it);
|
it = m_keyboards.erase(it);
|
||||||
keyboard->deleteLater();
|
keyboard->deleteLater();
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& o : val) {
|
for (const auto& o : val) {
|
||||||
const auto obj = o.toObject();
|
const auto obj = o.toObject();
|
||||||
const auto addr = obj.value("address").toString();
|
const auto addr = obj.value("address").toString();
|
||||||
|
|
||||||
auto it = std::find_if(m_keyboards.begin(), m_keyboards.end(), [addr](const HyprKeyboard* kb) {
|
auto it = std::find_if(
|
||||||
return kb->address() == addr;
|
m_keyboards.begin(),
|
||||||
});
|
m_keyboards.end(),
|
||||||
|
[addr](const HyprKeyboard* kb) { return kb->address() == addr; });
|
||||||
|
|
||||||
if (it != m_keyboards.end()) {
|
if (it != m_keyboards.end()) {
|
||||||
dirty |= (*it)->updateLastIpcObject(obj);
|
dirty |= (*it)->updateLastIpcObject(obj);
|
||||||
} else {
|
} else {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
m_keyboards << new HyprKeyboard(obj, this);
|
m_keyboards << new HyprKeyboard(obj, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
emit keyboardsChanged();
|
emit keyboardsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
return dirty;
|
return dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::internal::hypr
|
} // namespace ZShell::internal::hypr
|
||||||
|
|||||||
@@ -8,67 +8,72 @@
|
|||||||
namespace ZShell::internal::hypr {
|
namespace ZShell::internal::hypr {
|
||||||
|
|
||||||
class HyprKeyboard : public QObject {
|
class HyprKeyboard : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices")
|
QML_UNCREATABLE(
|
||||||
|
"HyprKeyboard instances can only be retrieved from a HyprDevices")
|
||||||
|
|
||||||
Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QString address READ address NOTIFY addressChanged)
|
QVariantHash lastIpcObject READ lastIpcObject NOTIFY
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
lastIpcObjectChanged)
|
||||||
Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged)
|
Q_PROPERTY(QString address READ address NOTIFY addressChanged)
|
||||||
Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged)
|
Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged)
|
||||||
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged)
|
Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged)
|
||||||
Q_PROPERTY(bool main READ main NOTIFY mainChanged)
|
Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged)
|
||||||
|
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged)
|
||||||
|
Q_PROPERTY(bool main READ main NOTIFY mainChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr);
|
explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QVariantHash lastIpcObject() const;
|
[[nodiscard]] QVariantHash lastIpcObject() const;
|
||||||
[[nodiscard]] QString address() const;
|
[[nodiscard]] QString address() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString layout() const;
|
[[nodiscard]] QString layout() const;
|
||||||
[[nodiscard]] QString activeKeymap() const;
|
[[nodiscard]] QString activeKeymap() const;
|
||||||
[[nodiscard]] bool capsLock() const;
|
[[nodiscard]] bool capsLock() const;
|
||||||
[[nodiscard]] bool numLock() const;
|
[[nodiscard]] bool numLock() const;
|
||||||
[[nodiscard]] bool main() const;
|
[[nodiscard]] bool main() const;
|
||||||
|
|
||||||
bool updateLastIpcObject(QJsonObject object);
|
bool updateLastIpcObject(QJsonObject object);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void lastIpcObjectChanged();
|
void lastIpcObjectChanged();
|
||||||
void addressChanged();
|
void addressChanged();
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void layoutChanged();
|
void layoutChanged();
|
||||||
void activeKeymapChanged();
|
void activeKeymapChanged();
|
||||||
void capsLockChanged();
|
void capsLockChanged();
|
||||||
void numLockChanged();
|
void numLockChanged();
|
||||||
void mainChanged();
|
void mainChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QJsonObject m_lastIpcObject;
|
QJsonObject m_lastIpcObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HyprDevices : public QObject {
|
class HyprDevices : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras")
|
QML_UNCREATABLE(
|
||||||
|
"HyprDevices instances can only be retrieved from a HyprExtras")
|
||||||
|
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged)
|
QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ
|
||||||
|
keyboards NOTIFY keyboardsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HyprDevices(QObject* parent = nullptr);
|
explicit HyprDevices(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards();
|
[[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards();
|
||||||
|
|
||||||
bool updateLastIpcObject(QJsonObject object);
|
bool updateLastIpcObject(QJsonObject object);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void keyboardsChanged();
|
void keyboardsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<HyprKeyboard*> m_keyboards;
|
QList<HyprKeyboard*> m_keyboards;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal::hypr
|
}
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ static QString luaArray(const QVariantList& list) {
|
|||||||
parts << luaValue(item);
|
parts << luaValue(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
|
||||||
|
QLatin1String(" }");
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString luaArray(const QStringList& list) {
|
static QString luaArray(const QStringList& list) {
|
||||||
@@ -75,7 +76,8 @@ static QString luaArray(const QStringList& list) {
|
|||||||
parts << luaEscapeString(item);
|
parts << luaEscapeString(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
|
||||||
|
QLatin1String(" }");
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString luaMapFromHash(const QVariantHash& hash) {
|
static QString luaMapFromHash(const QVariantHash& hash) {
|
||||||
@@ -83,10 +85,12 @@ static QString luaMapFromHash(const QVariantHash& hash) {
|
|||||||
parts.reserve(hash.size());
|
parts.reserve(hash.size());
|
||||||
|
|
||||||
for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
|
for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
|
||||||
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
|
parts << luaEscapeString(it.key()) + QLatin1String(" = ") +
|
||||||
|
luaValue(it.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
|
||||||
|
QLatin1String(" }");
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString luaMap(const QVariantMap& map) {
|
static QString luaMap(const QVariantMap& map) {
|
||||||
@@ -94,10 +98,12 @@ static QString luaMap(const QVariantMap& map) {
|
|||||||
parts.reserve(map.size());
|
parts.reserve(map.size());
|
||||||
|
|
||||||
for (auto it = map.cbegin(); it != map.cend(); ++it) {
|
for (auto it = map.cbegin(); it != map.cend(); ++it) {
|
||||||
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
|
parts << luaEscapeString(it.key()) + QLatin1String(" = ") +
|
||||||
|
luaValue(it.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) +
|
||||||
|
QLatin1String(" }");
|
||||||
}
|
}
|
||||||
|
|
||||||
static QString luaValue(const QVariant& v) {
|
static QString luaValue(const QVariant& v) {
|
||||||
@@ -143,7 +149,8 @@ static QString normalizeOptionPath(QString key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static QString buildHlConfigCall(const QString& key, const QVariant& value) {
|
static QString buildHlConfigCall(const QString& key, const QVariant& value) {
|
||||||
const auto parts = normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts);
|
const auto parts =
|
||||||
|
normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts);
|
||||||
if (parts.isEmpty()) {
|
if (parts.isEmpty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -189,7 +196,8 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) {
|
|||||||
|
|
||||||
const auto option = obj.value(QStringLiteral("option")).toString();
|
const auto option = obj.value(QStringLiteral("option")).toString();
|
||||||
|
|
||||||
if (option.contains(QStringLiteral("color")) || option.contains(QStringLiteral("col."))) {
|
if (option.contains(QStringLiteral("color")) ||
|
||||||
|
option.contains(QStringLiteral("col."))) {
|
||||||
return colorFromInt(static_cast<quint32>(value));
|
return colorFromInt(static_cast<quint32>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +242,8 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void insertNestedValue(QVariantMap& root, const QStringList& path, const QVariant& value) {
|
static void insertNestedValue(
|
||||||
|
QVariantMap& root, const QStringList& path, const QVariant& value) {
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -261,16 +270,19 @@ HyprExtras::HyprExtras(QObject* parent)
|
|||||||
, m_devices(new HyprDevices(this)) {
|
, m_devices(new HyprDevices(this)) {
|
||||||
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
|
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
if (his.isEmpty()) {
|
if (his.isEmpty()) {
|
||||||
qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
|
qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to "
|
||||||
|
"connect to Hyprland socket.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
|
auto hyprDir =
|
||||||
|
QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
|
||||||
if (!QDir(hyprDir).exists()) {
|
if (!QDir(hyprDir).exists()) {
|
||||||
hyprDir = QStringLiteral("/tmp/hypr/") + his;
|
hyprDir = QStringLiteral("/tmp/hypr/") + his;
|
||||||
|
|
||||||
if (!QDir(hyprDir).exists()) {
|
if (!QDir(hyprDir).exists()) {
|
||||||
qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket.";
|
qCWarning(lcHypr) << "Hyprland socket directory does not exist. "
|
||||||
|
"Unable to connect to Hyprland socket.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,9 +295,15 @@ HyprExtras::HyprExtras(QObject* parent)
|
|||||||
|
|
||||||
m_socket = new QLocalSocket(this);
|
m_socket = new QLocalSocket(this);
|
||||||
|
|
||||||
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
|
QObject::connect(
|
||||||
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged);
|
m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
|
||||||
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
|
QObject::connect(
|
||||||
|
m_socket,
|
||||||
|
&QLocalSocket::stateChanged,
|
||||||
|
this,
|
||||||
|
&HyprExtras::socketStateChanged);
|
||||||
|
QObject::connect(
|
||||||
|
m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
|
||||||
|
|
||||||
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
|
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
|
||||||
}
|
}
|
||||||
@@ -304,10 +322,11 @@ void HyprExtras::message(const QString& message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
makeRequest(message, [](bool success, const QByteArray& res) {
|
makeRequest(message, [](bool success, const QByteArray& res) {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res);
|
qCWarning(lcHypr)
|
||||||
}
|
<< "message: request error:" << QString::fromUtf8(res);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void HyprExtras::batchMessage(const QStringList& messages) {
|
void HyprExtras::batchMessage(const QStringList& messages) {
|
||||||
@@ -315,10 +334,12 @@ void HyprExtras::batchMessage(const QStringList& messages) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
|
makeRequest(
|
||||||
[](bool success, const QByteArray& res) {
|
QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
|
||||||
|
[](bool success, const QByteArray& res) {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res);
|
qCWarning(lcHypr)
|
||||||
|
<< "batchMessage: request error:" << QString::fromUtf8(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -342,11 +363,14 @@ void HyprExtras::applyOptions(const QVariantHash& options) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
|
makeRequest(
|
||||||
|
QStringLiteral("eval ") + calls.join(QLatin1String("; ")),
|
||||||
|
[this](bool success, const QByteArray& res) {
|
||||||
if (success) {
|
if (success) {
|
||||||
refreshOptions();
|
refreshOptions();
|
||||||
} else {
|
} else {
|
||||||
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res);
|
qCWarning(lcHypr)
|
||||||
|
<< "applyOptions: request error" << QString::fromUtf8(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -372,26 +396,26 @@ void HyprExtras::refreshOptions() {
|
|||||||
|
|
||||||
auto nextOptions = std::make_shared<QVariantMap>();
|
auto nextOptions = std::make_shared<QVariantMap>();
|
||||||
|
|
||||||
auto step = std::make_shared<std::function<void(int)> >();
|
auto step = std::make_shared<std::function<void(int)>>();
|
||||||
*step = [this, generation, nextOptions, step](int index) {
|
*step = [this, generation, nextOptions, step](int index) {
|
||||||
if (generation != m_optionsRefreshGeneration) {
|
if (generation != m_optionsRefreshGeneration) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= optionKeys.size()) {
|
||||||
|
if (m_options != *nextOptions) {
|
||||||
|
m_options = *nextOptions;
|
||||||
|
emit optionsChanged();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (index >= optionKeys.size()) {
|
const QString key = optionKeys.at(index);
|
||||||
if (m_options != *nextOptions) {
|
|
||||||
m_options = *nextOptions;
|
|
||||||
emit optionsChanged();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString key = optionKeys.at(index);
|
m_optionsRefresh = makeRequestJson(
|
||||||
|
QStringLiteral("getoption ") + key,
|
||||||
m_optionsRefresh = makeRequestJson(
|
[this, generation, nextOptions, step, index, key](
|
||||||
QStringLiteral("getoption ") + key,
|
bool success, const QJsonDocument& response) {
|
||||||
[this, generation, nextOptions, step, index, key](bool success, const QJsonDocument& response)
|
|
||||||
{
|
|
||||||
m_optionsRefresh.reset();
|
m_optionsRefresh.reset();
|
||||||
|
|
||||||
if (generation != m_optionsRefreshGeneration) {
|
if (generation != m_optionsRefreshGeneration) {
|
||||||
@@ -399,19 +423,26 @@ void HyprExtras::refreshOptions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success && response.isObject()) {
|
if (success && response.isObject()) {
|
||||||
const QVariant value = parseGetOptionValue(response.object());
|
const QVariant value =
|
||||||
|
parseGetOptionValue(response.object());
|
||||||
if (value.isValid()) {
|
if (value.isValid()) {
|
||||||
insertNestedValue(*nextOptions, key.split(QLatin1Char(':'), Qt::SkipEmptyParts), value);
|
insertNestedValue(
|
||||||
|
*nextOptions,
|
||||||
|
key.split(QLatin1Char(':'), Qt::SkipEmptyParts),
|
||||||
|
value);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(lcHypr) << "refreshOptions: getoption returned no usable value for" << key;
|
qCWarning(lcHypr) << "refreshOptions: getoption "
|
||||||
|
"returned no usable value for"
|
||||||
|
<< key;
|
||||||
}
|
}
|
||||||
} else if (!success) {
|
} else if (!success) {
|
||||||
qCWarning(lcHypr) << "refreshOptions: getoption request error for" << key;
|
qCWarning(lcHypr)
|
||||||
|
<< "refreshOptions: getoption request error for" << key;
|
||||||
}
|
}
|
||||||
|
|
||||||
(*step)(index + 1);
|
(*step)(index + 1);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
(*step)(0);
|
(*step)(0);
|
||||||
}
|
}
|
||||||
@@ -421,7 +452,9 @@ void HyprExtras::refreshDevices() {
|
|||||||
m_devicesRefresh->close();
|
m_devicesRefresh->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) {
|
m_devicesRefresh = makeRequestJson(
|
||||||
|
QStringLiteral("devices"),
|
||||||
|
[this](bool success, const QJsonDocument& response) {
|
||||||
m_devicesRefresh.reset();
|
m_devicesRefresh.reset();
|
||||||
if (success) {
|
if (success) {
|
||||||
m_devices->updateLastIpcObject(response.object());
|
m_devices->updateLastIpcObject(response.object());
|
||||||
@@ -431,15 +464,19 @@ void HyprExtras::refreshDevices() {
|
|||||||
|
|
||||||
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
|
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
|
||||||
if (!m_socketValid) {
|
if (!m_socketValid) {
|
||||||
qCWarning(lcHypr) << "socketError: unable to connect to Hyprland event socket:" << error;
|
qCWarning(lcHypr)
|
||||||
|
<< "socketError: unable to connect to Hyprland event socket:"
|
||||||
|
<< error;
|
||||||
} else {
|
} else {
|
||||||
qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error;
|
qCWarning(lcHypr) << "socketError: Hyprland event socket error:"
|
||||||
|
<< error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
|
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||||
if (state == QLocalSocket::UnconnectedState && m_socketValid) {
|
if (state == QLocalSocket::UnconnectedState && m_socketValid) {
|
||||||
qCWarning(lcHypr) << "socketStateChanged: Hyprland event socket disconnected.";
|
qCWarning(lcHypr)
|
||||||
|
<< "socketStateChanged: Hyprland event socket disconnected.";
|
||||||
}
|
}
|
||||||
|
|
||||||
m_socketValid = state == QLocalSocket::ConnectedState;
|
m_socketValid = state == QLocalSocket::ConnectedState;
|
||||||
@@ -452,7 +489,8 @@ void HyprExtras::readEvent() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
rawEvent.truncate(rawEvent.length() - 1);
|
rawEvent.truncate(rawEvent.length() - 1);
|
||||||
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
|
const auto event =
|
||||||
|
QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
|
||||||
handleEvent(QString::fromUtf8(event));
|
handleEvent(QString::fromUtf8(event));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,14 +504,18 @@ void HyprExtras::handleEvent(const QString& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HyprExtras::SocketPtr HyprExtras::makeRequestJson(
|
HyprExtras::SocketPtr HyprExtras::makeRequestJson(
|
||||||
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
|
const QString& request,
|
||||||
return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) {
|
const std::function<void(bool, QJsonDocument)>& callback) {
|
||||||
|
return makeRequest(
|
||||||
|
QStringLiteral("j/") + request,
|
||||||
|
[callback](bool success, const QByteArray& response) {
|
||||||
callback(success, QJsonDocument::fromJson(response));
|
callback(success, QJsonDocument::fromJson(response));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
HyprExtras::SocketPtr HyprExtras::makeRequest(
|
HyprExtras::SocketPtr HyprExtras::makeRequest(
|
||||||
const QString& request, const std::function<void(bool, QByteArray)>& callback) {
|
const QString& request,
|
||||||
|
const std::function<void(bool, QByteArray)>& callback) {
|
||||||
if (m_requestSocket.isEmpty()) {
|
if (m_requestSocket.isEmpty()) {
|
||||||
return SocketPtr();
|
return SocketPtr();
|
||||||
}
|
}
|
||||||
@@ -481,18 +523,24 @@ HyprExtras::SocketPtr HyprExtras::makeRequest(
|
|||||||
auto socket = SocketPtr::create(this);
|
auto socket = SocketPtr::create(this);
|
||||||
|
|
||||||
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
|
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
|
||||||
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
|
QObject::connect(
|
||||||
|
socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
|
||||||
const auto response = socket->readAll();
|
const auto response = socket->readAll();
|
||||||
callback(true, std::move(response));
|
callback(true, std::move(response));
|
||||||
socket->close();
|
socket->close();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket->write(request.toUtf8());
|
socket->write(request.toUtf8());
|
||||||
socket->flush();
|
socket->flush();
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) {
|
QObject::connect(
|
||||||
qCWarning(lcHypr) << "makeRequest: error making request:" << err << "| request:" << request;
|
socket.data(),
|
||||||
|
&QLocalSocket::errorOccurred,
|
||||||
|
this,
|
||||||
|
[=](QLocalSocket::LocalSocketError err) {
|
||||||
|
qCWarning(lcHypr) << "makeRequest: error making request:" << err
|
||||||
|
<< "| request:" << request;
|
||||||
callback(false, {});
|
callback(false, {});
|
||||||
socket->close();
|
socket->close();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,51 +15,56 @@ namespace ZShell::internal::hypr {
|
|||||||
class HyprDevices;
|
class HyprDevices;
|
||||||
|
|
||||||
class HyprExtras : public QObject {
|
class HyprExtras : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
Q_MOC_INCLUDE("hyprdevices.hpp")
|
Q_MOC_INCLUDE("hyprdevices.hpp")
|
||||||
|
|
||||||
Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged)
|
Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged)
|
||||||
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
|
Q_PROPERTY(
|
||||||
|
ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HyprExtras(QObject* parent = nullptr);
|
explicit HyprExtras(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QVariantMap options() const;
|
[[nodiscard]] QVariantMap options() const;
|
||||||
[[nodiscard]] HyprDevices* devices() const;
|
[[nodiscard]] HyprDevices* devices() const;
|
||||||
|
|
||||||
Q_INVOKABLE void message(const QString& message);
|
Q_INVOKABLE void message(const QString& message);
|
||||||
Q_INVOKABLE void batchMessage(const QStringList& messages);
|
Q_INVOKABLE void batchMessage(const QStringList& messages);
|
||||||
Q_INVOKABLE void applyOptions(const QVariantHash& options);
|
Q_INVOKABLE void applyOptions(const QVariantHash& options);
|
||||||
|
|
||||||
Q_INVOKABLE void refreshOptions();
|
Q_INVOKABLE void refreshOptions();
|
||||||
Q_INVOKABLE void refreshDevices();
|
Q_INVOKABLE void refreshDevices();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void optionsChanged();
|
void optionsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using SocketPtr = QSharedPointer<QLocalSocket>;
|
using SocketPtr = QSharedPointer<QLocalSocket>;
|
||||||
|
|
||||||
QString m_requestSocket;
|
QString m_requestSocket;
|
||||||
QString m_eventSocket;
|
QString m_eventSocket;
|
||||||
QLocalSocket* m_socket;
|
QLocalSocket* m_socket;
|
||||||
bool m_socketValid;
|
bool m_socketValid;
|
||||||
|
|
||||||
QVariantMap m_options;
|
QVariantMap m_options;
|
||||||
HyprDevices* const m_devices;
|
HyprDevices* const m_devices;
|
||||||
|
|
||||||
SocketPtr m_optionsRefresh;
|
SocketPtr m_optionsRefresh;
|
||||||
SocketPtr m_devicesRefresh;
|
SocketPtr m_devicesRefresh;
|
||||||
quint64 m_optionsRefreshGeneration = 0;
|
quint64 m_optionsRefreshGeneration = 0;
|
||||||
|
|
||||||
void socketError(QLocalSocket::LocalSocketError error) const;
|
void socketError(QLocalSocket::LocalSocketError error) const;
|
||||||
void socketStateChanged(QLocalSocket::LocalSocketState state);
|
void socketStateChanged(QLocalSocket::LocalSocketState state);
|
||||||
void readEvent();
|
void readEvent();
|
||||||
void handleEvent(const QString& event);
|
void handleEvent(const QString& event);
|
||||||
|
|
||||||
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback);
|
SocketPtr makeRequestJson(
|
||||||
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
|
const QString& request,
|
||||||
|
const std::function<void(bool, QJsonDocument)>& callback);
|
||||||
|
SocketPtr makeRequest(
|
||||||
|
const QString& request,
|
||||||
|
const std::function<void(bool, QByteArray)>& callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal::hypr
|
} // namespace ZShell::internal::hypr
|
||||||
|
|||||||
@@ -14,27 +14,29 @@ LidWatcher::LidWatcher(QObject* parent) : QObject(parent) {
|
|||||||
auto bus = QDBusConnection::systemBus();
|
auto bus = QDBusConnection::systemBus();
|
||||||
if (!bus.isConnected()) {
|
if (!bus.isConnected()) {
|
||||||
qCWarning(lcLidWatcher)
|
qCWarning(lcLidWatcher)
|
||||||
<< "Failed to connect to system bus:" << bus.lastError().message();
|
<< "Failed to connect to system bus:" << bus.lastError().message();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok = bus.connect("org.freedesktop.login1",
|
bool ok = bus.connect(
|
||||||
"/org/freedesktop/login1",
|
"org.freedesktop.login1",
|
||||||
"org.freedesktop.login1.Manager",
|
"/org/freedesktop/login1",
|
||||||
"PrepareForSleep",
|
"org.freedesktop.login1.Manager",
|
||||||
this,
|
"PrepareForSleep",
|
||||||
SLOT(handlePrepareForSleep(bool)));
|
this,
|
||||||
|
SLOT(handlePrepareForSleep(bool)));
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
qCWarning(lcLidWatcher)
|
qCWarning(lcLidWatcher)
|
||||||
<< "Failed to connect to PrepareForSleep signal:"
|
<< "Failed to connect to PrepareForSleep signal:"
|
||||||
<< bus.lastError().message();
|
<< bus.lastError().message();
|
||||||
}
|
}
|
||||||
|
|
||||||
QDBusInterface login1("org.freedesktop.login1",
|
QDBusInterface login1(
|
||||||
"/org/freedesktop/login1",
|
"org.freedesktop.login1",
|
||||||
"org.freedesktop.login1.Manager",
|
"/org/freedesktop/login1",
|
||||||
bus);
|
"org.freedesktop.login1.Manager",
|
||||||
|
bus);
|
||||||
const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto");
|
const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto");
|
||||||
if (!reply.isValid()) {
|
if (!reply.isValid()) {
|
||||||
qCWarning(lcLidWatcher) << "Failed to get session path";
|
qCWarning(lcLidWatcher) << "Failed to get session path";
|
||||||
@@ -42,28 +44,30 @@ LidWatcher::LidWatcher(QObject* parent) : QObject(parent) {
|
|||||||
}
|
}
|
||||||
const auto sessionPath = reply.value().path();
|
const auto sessionPath = reply.value().path();
|
||||||
|
|
||||||
ok = bus.connect("org.freedesktop.login1",
|
ok = bus.connect(
|
||||||
sessionPath,
|
"org.freedesktop.login1",
|
||||||
"org.freedesktop.login1.Session",
|
sessionPath,
|
||||||
"Lock",
|
"org.freedesktop.login1.Session",
|
||||||
this,
|
"Lock",
|
||||||
SLOT(handleLockRequested()));
|
this,
|
||||||
|
SLOT(handleLockRequested()));
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
qCWarning(lcLidWatcher)
|
qCWarning(lcLidWatcher)
|
||||||
<< "Failed to connect to Lock signal:" << bus.lastError().message();
|
<< "Failed to connect to Lock signal:" << bus.lastError().message();
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = bus.connect("org.freedesktop.login1",
|
ok = bus.connect(
|
||||||
sessionPath,
|
"org.freedesktop.login1",
|
||||||
"org.freedesktop.login1.Session",
|
sessionPath,
|
||||||
"Unlock",
|
"org.freedesktop.login1.Session",
|
||||||
this,
|
"Unlock",
|
||||||
SLOT(handleUnlockRequested()));
|
this,
|
||||||
|
SLOT(handleUnlockRequested()));
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:"
|
qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:"
|
||||||
<< bus.lastError().message();
|
<< bus.lastError().message();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,22 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class LidWatcher : public QObject {
|
class LidWatcher : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LidWatcher(QObject* parent = nullptr);
|
explicit LidWatcher(QObject* parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void aboutToSleep();
|
void aboutToSleep();
|
||||||
void resumed();
|
void resumed();
|
||||||
void lockRequested();
|
void lockRequested();
|
||||||
void unlockRequested();
|
void unlockRequested();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handlePrepareForSleep(bool sleep);
|
void handlePrepareForSleep(bool sleep);
|
||||||
void handleLockRequested();
|
void handleLockRequested();
|
||||||
void handleUnlockRequested();
|
void handleUnlockRequested();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr int TOTAL_DURATION_IN_MS = 1800;
|
constexpr int TOTAL_DURATION_IN_MS = 1800;
|
||||||
constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = { 533, 567, 850, 750 };
|
constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = {533, 567, 850, 750};
|
||||||
constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = { 1267, 1000, 333, 0 };
|
constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = {1267, 1000, 333, 0};
|
||||||
|
|
||||||
QEasingCurve curve(const QPointF& c1, const QPointF& c2) {
|
QEasingCurve curve(const QPointF& c1, const QPointF& c2) {
|
||||||
QEasingCurve curve(QEasingCurve::BezierSpline);
|
QEasingCurve curve(QEasingCurve::BezierSpline);
|
||||||
curve.addCubicBezierSegment(c1, c2, { 1.0, 1.0 });
|
curve.addCubicBezierSegment(c1, c2, {1.0, 1.0});
|
||||||
return curve;
|
return curve;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,11 +24,7 @@ qreal getFractionInRange(qreal playtime, int start, int duration) {
|
|||||||
namespace ZShell::controls {
|
namespace ZShell::controls {
|
||||||
|
|
||||||
LinearIndicatorSegment::LinearIndicatorSegment(int gap, QObject* parent)
|
LinearIndicatorSegment::LinearIndicatorSegment(int gap, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent), m_startFraction(0), m_endFraction(0), m_gapSize(gap) {}
|
||||||
, m_startFraction(0)
|
|
||||||
, m_endFraction(0)
|
|
||||||
, m_gapSize(gap) {
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal LinearIndicatorSegment::startFraction() const {
|
qreal LinearIndicatorSegment::startFraction() const {
|
||||||
return m_startFraction;
|
return m_startFraction;
|
||||||
@@ -45,24 +41,28 @@ int LinearIndicatorSegment::gapSize() const {
|
|||||||
LinearIndicatorManager::LinearIndicatorManager(QObject* parent)
|
LinearIndicatorManager::LinearIndicatorManager(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_interpolators({
|
, m_interpolators({
|
||||||
curve({ 0.2, 0.0 }, { 0.8, 1.0 }),
|
curve({0.2, 0.0}, {0.8, 1.0}),
|
||||||
curve({ 0.4, 0.0 }, { 1.0, 1.0 }),
|
curve({0.4, 0.0}, {1.0, 1.0}),
|
||||||
curve({ 0.0, 0.0 }, { 0.65, 1.0 }),
|
curve({0.0, 0.0}, {0.65, 1.0}),
|
||||||
curve({ 0.1, 0.0 }, { 0.45, 1.0 }),
|
curve({0.1, 0.0}, {0.45, 1.0}),
|
||||||
})
|
})
|
||||||
, m_progress(0)
|
, m_progress(0)
|
||||||
, m_completeEndProgress(0)
|
, m_completeEndProgress(0)
|
||||||
, m_gap(4)
|
, m_gap(4)
|
||||||
, m_activeIndicators({
|
, m_activeIndicators({
|
||||||
new LinearIndicatorSegment(m_gap, this),
|
new LinearIndicatorSegment(m_gap, this),
|
||||||
new LinearIndicatorSegment(m_gap, this),
|
new LinearIndicatorSegment(m_gap, this),
|
||||||
}) {
|
}) {
|
||||||
for (auto el : m_activeIndicators)
|
for (auto el : m_activeIndicators)
|
||||||
QObject::connect(this, &LinearIndicatorManager::updated, el, &LinearIndicatorSegment::updated);
|
QObject::connect(
|
||||||
|
this,
|
||||||
|
&LinearIndicatorManager::updated,
|
||||||
|
el,
|
||||||
|
&LinearIndicatorSegment::updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<LinearIndicatorSegment*> LinearIndicatorManager::activeIndicators() const {
|
QList<LinearIndicatorSegment*> LinearIndicatorManager::activeIndicators() const {
|
||||||
return { m_activeIndicators.cbegin(), m_activeIndicators.cend() };
|
return {m_activeIndicators.cbegin(), m_activeIndicators.cend()};
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal LinearIndicatorManager::progress() const {
|
qreal LinearIndicatorManager::progress() const {
|
||||||
@@ -98,12 +98,19 @@ void LinearIndicatorManager::update(qreal progress) {
|
|||||||
const auto di = i * 2;
|
const auto di = i * 2;
|
||||||
auto* const indicator = m_activeIndicators[i];
|
auto* const indicator = m_activeIndicators[i];
|
||||||
|
|
||||||
auto fraction = getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di], DURATION_TO_MOVE_SEGMENT_ENDS[di]);
|
auto fraction = getFractionInRange(
|
||||||
indicator->m_startFraction = std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0);
|
playtime,
|
||||||
|
DELAY_TO_MOVE_SEGMENT_ENDS[di],
|
||||||
|
DURATION_TO_MOVE_SEGMENT_ENDS[di]);
|
||||||
|
indicator->m_startFraction =
|
||||||
|
std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0);
|
||||||
|
|
||||||
fraction =
|
fraction = getFractionInRange(
|
||||||
getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di + 1], DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]);
|
playtime,
|
||||||
indicator->m_endFraction = std::clamp(m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0);
|
DELAY_TO_MOVE_SEGMENT_ENDS[di + 1],
|
||||||
|
DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]);
|
||||||
|
indicator->m_endFraction = std::clamp(
|
||||||
|
m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_progress = progress;
|
m_progress = progress;
|
||||||
|
|||||||
@@ -11,76 +11,80 @@ namespace ZShell::controls {
|
|||||||
class LinearIndicatorManager;
|
class LinearIndicatorManager;
|
||||||
|
|
||||||
class LinearIndicatorSegment : public QObject {
|
class LinearIndicatorSegment : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("LinearIndicatorSegments can only be retrieved from a "
|
QML_UNCREATABLE(
|
||||||
"LinearIndicatorManager.")
|
"LinearIndicatorSegments can only be retrieved from a "
|
||||||
|
"LinearIndicatorManager.")
|
||||||
|
|
||||||
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL)
|
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL)
|
||||||
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL)
|
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL)
|
||||||
Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL)
|
Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr);
|
explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr);
|
||||||
|
|
||||||
qreal startFraction() const;
|
qreal startFraction() const;
|
||||||
qreal endFraction() const;
|
qreal endFraction() const;
|
||||||
int gapSize() const;
|
int gapSize() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updated();
|
void updated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qreal m_startFraction;
|
qreal m_startFraction;
|
||||||
qreal m_endFraction;
|
qreal m_endFraction;
|
||||||
int m_gapSize;
|
int m_gapSize;
|
||||||
|
|
||||||
friend LinearIndicatorManager;
|
friend LinearIndicatorManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LinearIndicatorManager : public QObject {
|
class LinearIndicatorManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
QList<ZShell::controls::LinearIndicatorSegment*> activeIndicators READ activeIndicators CONSTANT FINAL)
|
QList<ZShell::controls::LinearIndicatorSegment*> activeIndicators READ
|
||||||
|
activeIndicators CONSTANT FINAL)
|
||||||
|
|
||||||
Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL)
|
Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL)
|
||||||
Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE updateCompleteEndProgress NOTIFY updated FINAL)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL)
|
qreal completeEndProgress READ completeEndProgress WRITE
|
||||||
|
updateCompleteEndProgress NOTIFY updated FINAL)
|
||||||
|
Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL)
|
||||||
|
|
||||||
Q_PROPERTY(qreal duration READ duration CONSTANT FINAL)
|
Q_PROPERTY(qreal duration READ duration CONSTANT FINAL)
|
||||||
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL)
|
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LinearIndicatorManager(QObject* parent = nullptr);
|
explicit LinearIndicatorManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
QList<LinearIndicatorSegment*> activeIndicators() const;
|
QList<LinearIndicatorSegment*> activeIndicators() const;
|
||||||
|
|
||||||
qreal progress() const;
|
qreal progress() const;
|
||||||
qreal completeEndProgress() const;
|
qreal completeEndProgress() const;
|
||||||
|
|
||||||
int gap() const;
|
int gap() const;
|
||||||
void setGap(int gap);
|
void setGap(int gap);
|
||||||
|
|
||||||
int duration() const;
|
int duration() const;
|
||||||
int completeEndDuration() const;
|
int completeEndDuration() const;
|
||||||
|
|
||||||
void update(qreal progress);
|
void update(qreal progress);
|
||||||
void updateCompleteEndProgress(qreal progress);
|
void updateCompleteEndProgress(qreal progress);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updated();
|
void updated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr int SEGMENTS = 2;
|
static constexpr int SEGMENTS = 2;
|
||||||
|
|
||||||
std::array<QEasingCurve, 4> m_interpolators;
|
std::array<QEasingCurve, 4> m_interpolators;
|
||||||
qreal m_progress;
|
qreal m_progress;
|
||||||
qreal m_completeEndProgress;
|
qreal m_completeEndProgress;
|
||||||
int m_gap;
|
int m_gap;
|
||||||
|
|
||||||
std::array<LinearIndicatorSegment*, SEGMENTS> m_activeIndicators;
|
std::array<LinearIndicatorSegment*, SEGMENTS> m_activeIndicators;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::controls
|
} // namespace ZShell::controls
|
||||||
|
|||||||
@@ -7,47 +7,46 @@
|
|||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
SparklineItem::SparklineItem(QQuickItem* parent)
|
SparklineItem::SparklineItem(QQuickItem* parent) : QQuickPaintedItem(parent) {
|
||||||
: QQuickPaintedItem(parent) {
|
|
||||||
setAntialiasing(true);
|
setAntialiasing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::paint(QPainter* painter) {
|
void SparklineItem::paint(QPainter* painter) {
|
||||||
const bool has1 = m_line1 && m_line1->count() >= 2;
|
const bool has1 = m_line1 && m_line1->count() >= 2;
|
||||||
const bool has2 = m_line2 && m_line2->count() >= 2;
|
const bool has2 = m_line2 && m_line2->count() >= 2;
|
||||||
if (!has1 && !has2)
|
if (!has1 && !has2) return;
|
||||||
return;
|
|
||||||
|
|
||||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
// Draw line1 first (behind), then line2 (in front)
|
// Draw line1 first (behind), then line2 (in front)
|
||||||
if (has1)
|
if (has1) drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha);
|
||||||
drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha);
|
if (has2) drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha);
|
||||||
if (has2)
|
|
||||||
drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha) {
|
void SparklineItem::drawLine(
|
||||||
if (m_historyLength < 2)
|
QPainter* painter,
|
||||||
return;
|
CircularBuffer* buffer,
|
||||||
|
const QColor& color,
|
||||||
|
qreal fillAlpha) {
|
||||||
|
if (m_historyLength < 2) return;
|
||||||
|
|
||||||
const qreal w = width();
|
const qreal w = width();
|
||||||
const qreal h = height();
|
const qreal h = height();
|
||||||
const int len = buffer->count();
|
const int len = buffer->count();
|
||||||
if (len < 2 || m_maxValue <= 0.0)
|
if (len < 2 || m_maxValue <= 0.0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const qreal stepX = w / static_cast<qreal>(m_historyLength - 1);
|
const qreal stepX = w / static_cast<qreal>(m_historyLength - 1);
|
||||||
const qreal startX = w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
|
const qreal startX =
|
||||||
|
w - (len - 1) * stepX - stepX * m_slideProgress + stepX;
|
||||||
|
|
||||||
const qreal strokePad = qCeil(m_lineWidth / 2);
|
const qreal strokePad = qCeil(m_lineWidth / 2);
|
||||||
const qreal curvePad = 3.0;
|
const qreal curvePad = 3.0;
|
||||||
const qreal topPad = strokePad + curvePad;
|
const qreal topPad = strokePad + curvePad;
|
||||||
const qreal bottomPad = strokePad;
|
const qreal bottomPad = strokePad;
|
||||||
const qreal plotTop = topPad;
|
const qreal plotTop = topPad;
|
||||||
const qreal plotBottom = h - bottomPad;
|
const qreal plotBottom = h - bottomPad;
|
||||||
const qreal fillBottom = h;
|
const qreal fillBottom = h;
|
||||||
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
|
const qreal plotH = qMax<qreal>(1.0, plotBottom - plotTop);
|
||||||
|
|
||||||
QVector<QPointF> points;
|
QVector<QPointF> points;
|
||||||
points.reserve(len);
|
points.reserve(len);
|
||||||
@@ -93,23 +92,22 @@ void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QC
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::connectBuffer(CircularBuffer* buffer) {
|
void SparklineItem::connectBuffer(CircularBuffer* buffer) {
|
||||||
if (!buffer)
|
if (!buffer) return;
|
||||||
return;
|
|
||||||
|
|
||||||
connect(buffer, &CircularBuffer::valuesChanged, this, [this]() {
|
connect(buffer, &CircularBuffer::valuesChanged, this, [this]() {
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
connect(buffer, &QObject::destroyed, this, [this, buffer]() {
|
connect(buffer, &QObject::destroyed, this, [this, buffer]() {
|
||||||
if (m_line1 == buffer) {
|
if (m_line1 == buffer) {
|
||||||
m_line1 = nullptr;
|
m_line1 = nullptr;
|
||||||
emit line1Changed();
|
emit line1Changed();
|
||||||
}
|
}
|
||||||
if (m_line2 == buffer) {
|
if (m_line2 == buffer) {
|
||||||
m_line2 = nullptr;
|
m_line2 = nullptr;
|
||||||
emit line2Changed();
|
emit line2Changed();
|
||||||
}
|
}
|
||||||
update();
|
update();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CircularBuffer* SparklineItem::line1() const {
|
CircularBuffer* SparklineItem::line1() const {
|
||||||
@@ -117,10 +115,8 @@ CircularBuffer* SparklineItem::line1() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLine1(CircularBuffer* buffer) {
|
void SparklineItem::setLine1(CircularBuffer* buffer) {
|
||||||
if (m_line1 == buffer)
|
if (m_line1 == buffer) return;
|
||||||
return;
|
if (m_line1) disconnect(m_line1, nullptr, this, nullptr);
|
||||||
if (m_line1)
|
|
||||||
disconnect(m_line1, nullptr, this, nullptr);
|
|
||||||
m_line1 = buffer;
|
m_line1 = buffer;
|
||||||
connectBuffer(buffer);
|
connectBuffer(buffer);
|
||||||
emit line1Changed();
|
emit line1Changed();
|
||||||
@@ -132,10 +128,8 @@ CircularBuffer* SparklineItem::line2() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLine2(CircularBuffer* buffer) {
|
void SparklineItem::setLine2(CircularBuffer* buffer) {
|
||||||
if (m_line2 == buffer)
|
if (m_line2 == buffer) return;
|
||||||
return;
|
if (m_line2) disconnect(m_line2, nullptr, this, nullptr);
|
||||||
if (m_line2)
|
|
||||||
disconnect(m_line2, nullptr, this, nullptr);
|
|
||||||
m_line2 = buffer;
|
m_line2 = buffer;
|
||||||
connectBuffer(buffer);
|
connectBuffer(buffer);
|
||||||
emit line2Changed();
|
emit line2Changed();
|
||||||
@@ -147,8 +141,7 @@ QColor SparklineItem::line1Color() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLine1Color(const QColor& color) {
|
void SparklineItem::setLine1Color(const QColor& color) {
|
||||||
if (m_line1Color == color)
|
if (m_line1Color == color) return;
|
||||||
return;
|
|
||||||
m_line1Color = color;
|
m_line1Color = color;
|
||||||
emit line1ColorChanged();
|
emit line1ColorChanged();
|
||||||
update();
|
update();
|
||||||
@@ -159,8 +152,7 @@ QColor SparklineItem::line2Color() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLine2Color(const QColor& color) {
|
void SparklineItem::setLine2Color(const QColor& color) {
|
||||||
if (m_line2Color == color)
|
if (m_line2Color == color) return;
|
||||||
return;
|
|
||||||
m_line2Color = color;
|
m_line2Color = color;
|
||||||
emit line2ColorChanged();
|
emit line2ColorChanged();
|
||||||
update();
|
update();
|
||||||
@@ -171,8 +163,7 @@ qreal SparklineItem::line1FillAlpha() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLine1FillAlpha(qreal alpha) {
|
void SparklineItem::setLine1FillAlpha(qreal alpha) {
|
||||||
if (qFuzzyCompare(m_line1FillAlpha, alpha))
|
if (qFuzzyCompare(m_line1FillAlpha, alpha)) return;
|
||||||
return;
|
|
||||||
m_line1FillAlpha = alpha;
|
m_line1FillAlpha = alpha;
|
||||||
emit line1FillAlphaChanged();
|
emit line1FillAlphaChanged();
|
||||||
update();
|
update();
|
||||||
@@ -183,8 +174,7 @@ qreal SparklineItem::line2FillAlpha() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLine2FillAlpha(qreal alpha) {
|
void SparklineItem::setLine2FillAlpha(qreal alpha) {
|
||||||
if (qFuzzyCompare(m_line2FillAlpha, alpha))
|
if (qFuzzyCompare(m_line2FillAlpha, alpha)) return;
|
||||||
return;
|
|
||||||
m_line2FillAlpha = alpha;
|
m_line2FillAlpha = alpha;
|
||||||
emit line2FillAlphaChanged();
|
emit line2FillAlphaChanged();
|
||||||
update();
|
update();
|
||||||
@@ -195,8 +185,7 @@ qreal SparklineItem::maxValue() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setMaxValue(qreal value) {
|
void SparklineItem::setMaxValue(qreal value) {
|
||||||
if (qFuzzyCompare(m_maxValue, value))
|
if (qFuzzyCompare(m_maxValue, value)) return;
|
||||||
return;
|
|
||||||
m_maxValue = value;
|
m_maxValue = value;
|
||||||
emit maxValueChanged();
|
emit maxValueChanged();
|
||||||
update();
|
update();
|
||||||
@@ -207,8 +196,7 @@ qreal SparklineItem::slideProgress() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setSlideProgress(qreal progress) {
|
void SparklineItem::setSlideProgress(qreal progress) {
|
||||||
if (qFuzzyCompare(m_slideProgress, progress))
|
if (qFuzzyCompare(m_slideProgress, progress)) return;
|
||||||
return;
|
|
||||||
m_slideProgress = progress;
|
m_slideProgress = progress;
|
||||||
emit slideProgressChanged();
|
emit slideProgressChanged();
|
||||||
update();
|
update();
|
||||||
@@ -219,8 +207,7 @@ int SparklineItem::historyLength() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setHistoryLength(int length) {
|
void SparklineItem::setHistoryLength(int length) {
|
||||||
if (m_historyLength == length)
|
if (m_historyLength == length) return;
|
||||||
return;
|
|
||||||
m_historyLength = length;
|
m_historyLength = length;
|
||||||
emit historyLengthChanged();
|
emit historyLengthChanged();
|
||||||
update();
|
update();
|
||||||
@@ -231,8 +218,7 @@ qreal SparklineItem::lineWidth() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SparklineItem::setLineWidth(qreal width) {
|
void SparklineItem::setLineWidth(qreal width) {
|
||||||
if (qFuzzyCompare(m_lineWidth, width))
|
if (qFuzzyCompare(m_lineWidth, width)) return;
|
||||||
return;
|
|
||||||
m_lineWidth = width;
|
m_lineWidth = width;
|
||||||
emit lineWidthChanged();
|
emit lineWidthChanged();
|
||||||
update();
|
update();
|
||||||
|
|||||||
@@ -10,81 +10,102 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class SparklineItem : public QQuickPaintedItem {
|
class SparklineItem : public QQuickPaintedItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed)
|
CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed)
|
||||||
Q_PROPERTY(QColor line1Color READ line1Color WRITE setLine1Color NOTIFY line1ColorChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QColor line2Color READ line2Color WRITE setLine2Color NOTIFY line2ColorChanged)
|
CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed)
|
||||||
Q_PROPERTY(qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY line1FillAlphaChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY line2FillAlphaChanged)
|
QColor line1Color READ line1Color WRITE setLine1Color NOTIFY
|
||||||
Q_PROPERTY(qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
|
line1ColorChanged)
|
||||||
Q_PROPERTY(qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY slideProgressChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(int historyLength READ historyLength WRITE setHistoryLength NOTIFY historyLengthChanged)
|
QColor line2Color READ line2Color WRITE setLine2Color NOTIFY
|
||||||
Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged)
|
line2ColorChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY
|
||||||
|
line1FillAlphaChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY
|
||||||
|
line2FillAlphaChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY
|
||||||
|
slideProgressChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
int historyLength READ historyLength WRITE setHistoryLength NOTIFY
|
||||||
|
historyLengthChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY
|
||||||
|
lineWidthChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SparklineItem(QQuickItem* parent = nullptr);
|
explicit SparklineItem(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
void paint(QPainter* painter) override;
|
void paint(QPainter* painter) override;
|
||||||
|
|
||||||
[[nodiscard]] CircularBuffer* line1() const;
|
[[nodiscard]] CircularBuffer* line1() const;
|
||||||
void setLine1(CircularBuffer* buffer);
|
void setLine1(CircularBuffer* buffer);
|
||||||
|
|
||||||
[[nodiscard]] CircularBuffer* line2() const;
|
[[nodiscard]] CircularBuffer* line2() const;
|
||||||
void setLine2(CircularBuffer* buffer);
|
void setLine2(CircularBuffer* buffer);
|
||||||
|
|
||||||
[[nodiscard]] QColor line1Color() const;
|
[[nodiscard]] QColor line1Color() const;
|
||||||
void setLine1Color(const QColor& color);
|
void setLine1Color(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] QColor line2Color() const;
|
[[nodiscard]] QColor line2Color() const;
|
||||||
void setLine2Color(const QColor& color);
|
void setLine2Color(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] qreal line1FillAlpha() const;
|
[[nodiscard]] qreal line1FillAlpha() const;
|
||||||
void setLine1FillAlpha(qreal alpha);
|
void setLine1FillAlpha(qreal alpha);
|
||||||
|
|
||||||
[[nodiscard]] qreal line2FillAlpha() const;
|
[[nodiscard]] qreal line2FillAlpha() const;
|
||||||
void setLine2FillAlpha(qreal alpha);
|
void setLine2FillAlpha(qreal alpha);
|
||||||
|
|
||||||
[[nodiscard]] qreal maxValue() const;
|
[[nodiscard]] qreal maxValue() const;
|
||||||
void setMaxValue(qreal value);
|
void setMaxValue(qreal value);
|
||||||
|
|
||||||
[[nodiscard]] qreal slideProgress() const;
|
[[nodiscard]] qreal slideProgress() const;
|
||||||
void setSlideProgress(qreal progress);
|
void setSlideProgress(qreal progress);
|
||||||
|
|
||||||
[[nodiscard]] int historyLength() const;
|
[[nodiscard]] int historyLength() const;
|
||||||
void setHistoryLength(int length);
|
void setHistoryLength(int length);
|
||||||
|
|
||||||
[[nodiscard]] qreal lineWidth() const;
|
[[nodiscard]] qreal lineWidth() const;
|
||||||
void setLineWidth(qreal width);
|
void setLineWidth(qreal width);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void line1Changed();
|
void line1Changed();
|
||||||
void line2Changed();
|
void line2Changed();
|
||||||
void line1ColorChanged();
|
void line1ColorChanged();
|
||||||
void line2ColorChanged();
|
void line2ColorChanged();
|
||||||
void line1FillAlphaChanged();
|
void line1FillAlphaChanged();
|
||||||
void line2FillAlphaChanged();
|
void line2FillAlphaChanged();
|
||||||
void maxValueChanged();
|
void maxValueChanged();
|
||||||
void slideProgressChanged();
|
void slideProgressChanged();
|
||||||
void historyLengthChanged();
|
void historyLengthChanged();
|
||||||
void lineWidthChanged();
|
void lineWidthChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha);
|
void drawLine(
|
||||||
void connectBuffer(CircularBuffer* buffer);
|
QPainter* painter,
|
||||||
|
CircularBuffer* buffer,
|
||||||
|
const QColor& color,
|
||||||
|
qreal fillAlpha);
|
||||||
|
void connectBuffer(CircularBuffer* buffer);
|
||||||
|
|
||||||
CircularBuffer* m_line1 = nullptr;
|
CircularBuffer* m_line1 = nullptr;
|
||||||
CircularBuffer* m_line2 = nullptr;
|
CircularBuffer* m_line2 = nullptr;
|
||||||
QColor m_line1Color;
|
QColor m_line1Color;
|
||||||
QColor m_line2Color;
|
QColor m_line2Color;
|
||||||
qreal m_line1FillAlpha = 0.15;
|
qreal m_line1FillAlpha = 0.15;
|
||||||
qreal m_line2FillAlpha = 0.2;
|
qreal m_line2FillAlpha = 0.2;
|
||||||
qreal m_maxValue = 1024.0;
|
qreal m_maxValue = 1024.0;
|
||||||
qreal m_slideProgress = 0.0;
|
qreal m_slideProgress = 0.0;
|
||||||
int m_historyLength = 30;
|
int m_historyLength = 30;
|
||||||
qreal m_lineWidth = 2.0;
|
qreal m_lineWidth = 2.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -9,14 +9,12 @@
|
|||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
VisualizerBars::VisualizerBars(QQuickItem* parent)
|
VisualizerBars::VisualizerBars(QQuickItem* parent) : QQuickPaintedItem(parent) {
|
||||||
: QQuickPaintedItem(parent) {
|
|
||||||
setAntialiasing(true);
|
setAntialiasing(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::advance(qreal dt) {
|
void VisualizerBars::advance(qreal dt) {
|
||||||
if (m_displayValues.isEmpty() || m_settled)
|
if (m_displayValues.isEmpty() || m_settled) return;
|
||||||
return;
|
|
||||||
|
|
||||||
// dt is in seconds (from FrameAnimation.frameTime), convert to ms
|
// dt is in seconds (from FrameAnimation.frameTime), convert to ms
|
||||||
const qreal dtMs = dt * 1000.0;
|
const qreal dtMs = dt * 1000.0;
|
||||||
@@ -45,8 +43,7 @@ void VisualizerBars::advance(qreal dt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::paint(QPainter* painter) {
|
void VisualizerBars::paint(QPainter* painter) {
|
||||||
if (m_displayValues.isEmpty())
|
if (m_displayValues.isEmpty()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||||
painter->setPen(Qt::NoPen);
|
painter->setPen(Qt::NoPen);
|
||||||
@@ -68,15 +65,13 @@ void VisualizerBars::drawSide(QPainter* painter, bool rightSide) {
|
|||||||
const qreal h = height();
|
const qreal h = height();
|
||||||
const auto count = m_displayValues.size();
|
const auto count = m_displayValues.size();
|
||||||
|
|
||||||
if (count == 0)
|
if (count == 0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const qreal sideWidth = w * 0.4;
|
const qreal sideWidth = w * 0.4;
|
||||||
const qreal slotWidth = sideWidth / static_cast<qreal>(count);
|
const qreal slotWidth = sideWidth / static_cast<qreal>(count);
|
||||||
const qreal barWidth = slotWidth - m_spacing;
|
const qreal barWidth = slotWidth - m_spacing;
|
||||||
|
|
||||||
if (barWidth <= 0)
|
if (barWidth <= 0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const qreal sideOffset = rightSide ? w * 0.6 : 0;
|
const qreal sideOffset = rightSide ? w * 0.6 : 0;
|
||||||
const qreal maxBarHeight = h * 0.4;
|
const qreal maxBarHeight = h * 0.4;
|
||||||
@@ -86,12 +81,11 @@ void VisualizerBars::drawSide(QPainter* painter, bool rightSide) {
|
|||||||
const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0);
|
const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0);
|
||||||
const qreal barHeight = value * maxBarHeight;
|
const qreal barHeight = value * maxBarHeight;
|
||||||
|
|
||||||
if (barHeight <= 0)
|
if (barHeight <= 0) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset;
|
const qreal x = static_cast<qreal>(i) * slotWidth + sideOffset;
|
||||||
const qreal y = h - barHeight;
|
const qreal y = h - barHeight;
|
||||||
const qreal r = std::min({ m_rounding, barWidth / 2.0, barHeight });
|
const qreal r = std::min({m_rounding, barWidth / 2.0, barHeight});
|
||||||
|
|
||||||
QPainterPath path;
|
QPainterPath path;
|
||||||
path.moveTo(x, h);
|
path.moveTo(x, h);
|
||||||
@@ -141,8 +135,7 @@ QColor VisualizerBars::primaryColor() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::setPrimaryColor(const QColor& color) {
|
void VisualizerBars::setPrimaryColor(const QColor& color) {
|
||||||
if (m_primaryColor == color)
|
if (m_primaryColor == color) return;
|
||||||
return;
|
|
||||||
m_primaryColor = color;
|
m_primaryColor = color;
|
||||||
emit primaryColorChanged();
|
emit primaryColorChanged();
|
||||||
update();
|
update();
|
||||||
@@ -153,8 +146,7 @@ QColor VisualizerBars::secondaryColor() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::setSecondaryColor(const QColor& color) {
|
void VisualizerBars::setSecondaryColor(const QColor& color) {
|
||||||
if (m_secondaryColor == color)
|
if (m_secondaryColor == color) return;
|
||||||
return;
|
|
||||||
m_secondaryColor = color;
|
m_secondaryColor = color;
|
||||||
emit secondaryColorChanged();
|
emit secondaryColorChanged();
|
||||||
update();
|
update();
|
||||||
@@ -165,8 +157,7 @@ qreal VisualizerBars::rounding() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::setRounding(qreal rounding) {
|
void VisualizerBars::setRounding(qreal rounding) {
|
||||||
if (qFuzzyCompare(m_rounding, rounding))
|
if (qFuzzyCompare(m_rounding, rounding)) return;
|
||||||
return;
|
|
||||||
m_rounding = rounding;
|
m_rounding = rounding;
|
||||||
emit roundingChanged();
|
emit roundingChanged();
|
||||||
update();
|
update();
|
||||||
@@ -177,8 +168,7 @@ qreal VisualizerBars::spacing() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::setSpacing(qreal spacing) {
|
void VisualizerBars::setSpacing(qreal spacing) {
|
||||||
if (qFuzzyCompare(m_spacing, spacing))
|
if (qFuzzyCompare(m_spacing, spacing)) return;
|
||||||
return;
|
|
||||||
m_spacing = spacing;
|
m_spacing = spacing;
|
||||||
emit spacingChanged();
|
emit spacingChanged();
|
||||||
update();
|
update();
|
||||||
@@ -189,8 +179,7 @@ int VisualizerBars::animationDuration() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VisualizerBars::setAnimationDuration(int duration) {
|
void VisualizerBars::setAnimationDuration(int duration) {
|
||||||
if (m_animationDuration == duration)
|
if (m_animationDuration == duration) return;
|
||||||
return;
|
|
||||||
m_animationDuration = duration;
|
m_animationDuration = duration;
|
||||||
emit animationDurationChanged();
|
emit animationDurationChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,64 +9,72 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class VisualizerBars : public QQuickPaintedItem {
|
class VisualizerBars : public QQuickPaintedItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QVector<double> values READ values WRITE setValues NOTIFY valuesChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY primaryColorChanged)
|
QVector<double> values READ values WRITE setValues NOTIFY valuesChanged)
|
||||||
Q_PROPERTY(QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY secondaryColorChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged)
|
QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY
|
||||||
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
primaryColorChanged)
|
||||||
Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged)
|
QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY
|
||||||
|
secondaryColorChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged)
|
||||||
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
int animationDuration READ animationDuration WRITE setAnimationDuration
|
||||||
|
NOTIFY animationDurationChanged)
|
||||||
|
Q_PROPERTY(bool settled READ settled NOTIFY settledChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit VisualizerBars(QQuickItem* parent = nullptr);
|
explicit VisualizerBars(QQuickItem* parent = nullptr);
|
||||||
|
|
||||||
void paint(QPainter* painter) override;
|
void paint(QPainter* painter) override;
|
||||||
|
|
||||||
Q_INVOKABLE void advance(qreal dt);
|
Q_INVOKABLE void advance(qreal dt);
|
||||||
|
|
||||||
[[nodiscard]] QVector<double> values() const;
|
[[nodiscard]] QVector<double> values() const;
|
||||||
void setValues(const QVector<double>& values);
|
void setValues(const QVector<double>& values);
|
||||||
|
|
||||||
[[nodiscard]] QColor primaryColor() const;
|
[[nodiscard]] QColor primaryColor() const;
|
||||||
void setPrimaryColor(const QColor& color);
|
void setPrimaryColor(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] QColor secondaryColor() const;
|
[[nodiscard]] QColor secondaryColor() const;
|
||||||
void setSecondaryColor(const QColor& color);
|
void setSecondaryColor(const QColor& color);
|
||||||
|
|
||||||
[[nodiscard]] qreal rounding() const;
|
[[nodiscard]] qreal rounding() const;
|
||||||
void setRounding(qreal rounding);
|
void setRounding(qreal rounding);
|
||||||
|
|
||||||
[[nodiscard]] qreal spacing() const;
|
[[nodiscard]] qreal spacing() const;
|
||||||
void setSpacing(qreal spacing);
|
void setSpacing(qreal spacing);
|
||||||
|
|
||||||
[[nodiscard]] int animationDuration() const;
|
[[nodiscard]] int animationDuration() const;
|
||||||
void setAnimationDuration(int duration);
|
void setAnimationDuration(int duration);
|
||||||
|
|
||||||
[[nodiscard]] bool settled() const;
|
[[nodiscard]] bool settled() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valuesChanged();
|
void valuesChanged();
|
||||||
void primaryColorChanged();
|
void primaryColorChanged();
|
||||||
void secondaryColorChanged();
|
void secondaryColorChanged();
|
||||||
void roundingChanged();
|
void roundingChanged();
|
||||||
void spacingChanged();
|
void spacingChanged();
|
||||||
void animationDurationChanged();
|
void animationDurationChanged();
|
||||||
void settledChanged();
|
void settledChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void drawSide(QPainter* painter, bool rightSide);
|
void drawSide(QPainter* painter, bool rightSide);
|
||||||
|
|
||||||
QVector<double> m_targetValues;
|
QVector<double> m_targetValues;
|
||||||
QVector<double> m_displayValues;
|
QVector<double> m_displayValues;
|
||||||
QColor m_primaryColor;
|
QColor m_primaryColor;
|
||||||
QColor m_secondaryColor;
|
QColor m_secondaryColor;
|
||||||
qreal m_rounding = 0.0;
|
qreal m_rounding = 0.0;
|
||||||
qreal m_spacing = 0.0;
|
qreal m_spacing = 0.0;
|
||||||
int m_animationDuration = 200;
|
int m_animationDuration = 200;
|
||||||
bool m_settled = true;
|
bool m_settled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -7,15 +7,16 @@
|
|||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
#include <QSGImageNode>
|
#include <QSGImageNode>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
#include <set>
|
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
WallpaperImage::WallpaperImage(QQuickItem *parent)
|
WallpaperImage::WallpaperImage(QQuickItem* parent) : QQuickItem(parent) {
|
||||||
: QQuickItem(parent)
|
|
||||||
{
|
|
||||||
setFlag(ItemHasContents, true);
|
setFlag(ItemHasContents, true);
|
||||||
connect(&m_imageWatcher, &QFutureWatcher<QImage>::finished, this, &WallpaperImage::handleImageLoaded);
|
connect(
|
||||||
|
&m_imageWatcher,
|
||||||
|
&QFutureWatcher<QImage>::finished,
|
||||||
|
this,
|
||||||
|
&WallpaperImage::handleImageLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
WallpaperImage::~WallpaperImage() {
|
WallpaperImage::~WallpaperImage() {
|
||||||
@@ -23,14 +24,13 @@ WallpaperImage::~WallpaperImage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WallpaperImage::setStatus(const Status s) {
|
void WallpaperImage::setStatus(const Status s) {
|
||||||
if (m_status == s)
|
if (m_status == s) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_status = s;
|
m_status = s;
|
||||||
emit statusChanged();
|
emit statusChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallpaperImage::setSource(const QUrl &source) {
|
void WallpaperImage::setSource(const QUrl& source) {
|
||||||
if (m_source == source) return;
|
if (m_source == source) return;
|
||||||
m_source = source;
|
m_source = source;
|
||||||
emit sourceChanged();
|
emit sourceChanged();
|
||||||
@@ -39,7 +39,7 @@ void WallpaperImage::setSource(const QUrl &source) {
|
|||||||
loadImage();
|
loadImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallpaperImage::setScreenResolution(const QSize &screenResolution) {
|
void WallpaperImage::setScreenResolution(const QSize& screenResolution) {
|
||||||
if (m_screenResolution == screenResolution) return;
|
if (m_screenResolution == screenResolution) return;
|
||||||
m_screenResolution = screenResolution;
|
m_screenResolution = screenResolution;
|
||||||
emit screenResolutionChanged();
|
emit screenResolutionChanged();
|
||||||
@@ -86,11 +86,16 @@ void WallpaperImage::setCropHeight(qreal h) {
|
|||||||
QString WallpaperImage::getCacheFilePath() const {
|
QString WallpaperImage::getCacheFilePath() const {
|
||||||
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
|
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
|
||||||
|
|
||||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache";
|
QString cachePath =
|
||||||
|
QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) +
|
||||||
|
"/zshell/imagecache";
|
||||||
QDir().mkpath(cachePath);
|
QDir().mkpath(cachePath);
|
||||||
|
|
||||||
QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height());
|
QString id = m_source.toString() + "_" +
|
||||||
QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
|
QString::number(m_screenResolution.width()) + "x" +
|
||||||
|
QString::number(m_screenResolution.height());
|
||||||
|
QByteArray hash =
|
||||||
|
QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
|
||||||
|
|
||||||
return cachePath + "/" + hash + ".png";
|
return cachePath + "/" + hash + ".png";
|
||||||
}
|
}
|
||||||
@@ -102,7 +107,8 @@ void WallpaperImage::loadImage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString cacheFile = getCacheFilePath();
|
QString cacheFile = getCacheFilePath();
|
||||||
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString();
|
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile()
|
||||||
|
: m_source.toString();
|
||||||
|
|
||||||
if (sourceFile.startsWith("qrc:/")) {
|
if (sourceFile.startsWith("qrc:/")) {
|
||||||
sourceFile = sourceFile.mid(3);
|
sourceFile = sourceFile.mid(3);
|
||||||
@@ -110,8 +116,10 @@ void WallpaperImage::loadImage() {
|
|||||||
|
|
||||||
QSize targetRes = m_screenResolution;
|
QSize targetRes = m_screenResolution;
|
||||||
|
|
||||||
QFuture<QImage> future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
|
QFuture<QImage> future =
|
||||||
if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) {
|
QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
|
||||||
|
if (!targetRes.isEmpty() && !cacheFile.isEmpty() &&
|
||||||
|
QFileInfo::exists(cacheFile)) {
|
||||||
QImage cached(cacheFile);
|
QImage cached(cacheFile);
|
||||||
if (!cached.isNull()) return cached;
|
if (!cached.isNull()) return cached;
|
||||||
}
|
}
|
||||||
@@ -123,8 +131,12 @@ void WallpaperImage::loadImage() {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (original.width() > targetRes.width() || original.height() > targetRes.height()) {
|
if (original.width() > targetRes.width() ||
|
||||||
QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
|
original.height() > targetRes.height()) {
|
||||||
|
QImage scaled = original.scaled(
|
||||||
|
targetRes,
|
||||||
|
Qt::KeepAspectRatioByExpanding,
|
||||||
|
Qt::SmoothTransformation);
|
||||||
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
|
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
|
||||||
return scaled;
|
return scaled;
|
||||||
}
|
}
|
||||||
@@ -145,8 +157,9 @@ void WallpaperImage::handleImageLoaded() {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
|
QSGNode* WallpaperImage::updatePaintNode(
|
||||||
auto *node = static_cast<QSGImageNode *>(oldNode);
|
QSGNode* oldNode, UpdatePaintNodeData*) {
|
||||||
|
auto* node = static_cast<QSGImageNode*>(oldNode);
|
||||||
|
|
||||||
if (m_image.isNull()) {
|
if (m_image.isNull()) {
|
||||||
delete node;
|
delete node;
|
||||||
@@ -159,7 +172,8 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
|
|||||||
|
|
||||||
if (m_textureDirty) {
|
if (m_textureDirty) {
|
||||||
if (m_texture) delete m_texture;
|
if (m_texture) delete m_texture;
|
||||||
m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel);
|
m_texture = window()->createTextureFromImage(
|
||||||
|
m_image, QQuickWindow::TextureHasAlphaChannel);
|
||||||
m_textureDirty = false;
|
m_textureDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +189,7 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
|
|||||||
m_cropX * m_texture->textureSize().width(),
|
m_cropX * m_texture->textureSize().width(),
|
||||||
m_cropY * m_texture->textureSize().height(),
|
m_cropY * m_texture->textureSize().height(),
|
||||||
cW * m_texture->textureSize().width(),
|
cW * m_texture->textureSize().width(),
|
||||||
cH * m_texture->textureSize().height()
|
cH * m_texture->textureSize().height());
|
||||||
);
|
|
||||||
|
|
||||||
QRectF bounds = boundingRect();
|
QRectF bounds = boundingRect();
|
||||||
if (bounds.isEmpty() || reqRect.isEmpty()) return node;
|
if (bounds.isEmpty() || reqRect.isEmpty()) return node;
|
||||||
@@ -202,25 +215,23 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
|
|||||||
sourceRect.x() / m_texture->textureSize().width(),
|
sourceRect.x() / m_texture->textureSize().width(),
|
||||||
sourceRect.y() / m_texture->textureSize().height(),
|
sourceRect.y() / m_texture->textureSize().height(),
|
||||||
sourceRect.width() / m_texture->textureSize().width(),
|
sourceRect.width() / m_texture->textureSize().width(),
|
||||||
sourceRect.height() / m_texture->textureSize().height()
|
sourceRect.height() / m_texture->textureSize().height());
|
||||||
);
|
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
auto updateIfChanged = [&](qreal &dst, qreal value) {
|
auto updateIfChanged = [&](qreal& dst, qreal value) {
|
||||||
if (!qFuzzyCompare(dst, value)) {
|
if (!qFuzzyCompare(dst, value)) {
|
||||||
dst = value;
|
dst = value;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateIfChanged(m_actualCropX, normalizedActual.x());
|
updateIfChanged(m_actualCropX, normalizedActual.x());
|
||||||
updateIfChanged(m_actualCropY, normalizedActual.y());
|
updateIfChanged(m_actualCropY, normalizedActual.y());
|
||||||
updateIfChanged(m_actualCropWidth, normalizedActual.width());
|
updateIfChanged(m_actualCropWidth, normalizedActual.width());
|
||||||
updateIfChanged(m_actualCropHeight, normalizedActual.height());
|
updateIfChanged(m_actualCropHeight, normalizedActual.height());
|
||||||
|
|
||||||
if (changed)
|
if (changed) emit actualCropChanged();
|
||||||
emit actualCropChanged();
|
|
||||||
|
|
||||||
node->setSourceRect(sourceRect);
|
node->setSourceRect(sourceRect);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,129 +11,109 @@
|
|||||||
namespace ZShell::internal {
|
namespace ZShell::internal {
|
||||||
|
|
||||||
class WallpaperImage : public QQuickItem {
|
class WallpaperImage : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_NAMED_ELEMENT(WallpaperImage)
|
QML_NAMED_ELEMENT(WallpaperImage)
|
||||||
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
|
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
|
||||||
Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
|
QSize screenResolution READ screenResolution WRITE setScreenResolution
|
||||||
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
NOTIFY screenResolutionChanged)
|
||||||
|
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
|
||||||
|
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
|
||||||
|
|
||||||
Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged)
|
Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged)
|
||||||
Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged)
|
Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged)
|
||||||
Q_PROPERTY(qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged)
|
qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged)
|
||||||
|
|
||||||
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
|
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
|
||||||
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
|
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
|
||||||
Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged)
|
qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY
|
||||||
|
cropWidthChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY
|
||||||
|
cropHeightChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WallpaperImage(QQuickItem *parent = nullptr);
|
explicit WallpaperImage(QQuickItem* parent = nullptr);
|
||||||
~WallpaperImage() override;
|
~WallpaperImage() override;
|
||||||
|
|
||||||
enum Status {
|
enum Status { Null, Ready, Loading, Error };
|
||||||
Null,
|
Q_ENUM(Status)
|
||||||
Ready,
|
|
||||||
Loading,
|
|
||||||
Error
|
|
||||||
};
|
|
||||||
Q_ENUM(Status)
|
|
||||||
|
|
||||||
Status status() const {
|
Status status() const { return m_status; }
|
||||||
return m_status;
|
|
||||||
}
|
|
||||||
|
|
||||||
QUrl source() const {
|
QUrl source() const { return m_source; }
|
||||||
return m_source;
|
void setSource(const QUrl& source);
|
||||||
}
|
|
||||||
void setSource(const QUrl &source);
|
|
||||||
|
|
||||||
QSize screenResolution() const {
|
QSize screenResolution() const { return m_screenResolution; }
|
||||||
return m_screenResolution;
|
void setScreenResolution(const QSize& screenResolution);
|
||||||
}
|
|
||||||
void setScreenResolution(const QSize &screenResolution);
|
|
||||||
|
|
||||||
qreal zoom() const {
|
qreal zoom() const { return m_zoom; }
|
||||||
return m_zoom;
|
void setZoom(qreal zoom);
|
||||||
}
|
|
||||||
void setZoom(qreal zoom);
|
|
||||||
|
|
||||||
qreal actualCropX() const {
|
qreal actualCropX() const { return m_actualCropX; }
|
||||||
return m_actualCropX;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal actualCropY() const {
|
qreal actualCropY() const { return m_actualCropY; }
|
||||||
return m_actualCropY;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal actualCropWidth() const {
|
qreal actualCropWidth() const { return m_actualCropWidth; }
|
||||||
return m_actualCropWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal actualCropHeight() const {
|
qreal actualCropHeight() const { return m_actualCropHeight; }
|
||||||
return m_actualCropHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal cropX() const {
|
qreal cropX() const { return m_cropX; }
|
||||||
return m_cropX;
|
void setCropX(qreal x);
|
||||||
}
|
|
||||||
void setCropX(qreal x);
|
|
||||||
|
|
||||||
qreal cropY() const {
|
qreal cropY() const { return m_cropY; }
|
||||||
return m_cropY;
|
void setCropY(qreal y);
|
||||||
}
|
|
||||||
void setCropY(qreal y);
|
|
||||||
|
|
||||||
qreal cropWidth() const {
|
qreal cropWidth() const { return m_cropWidth; }
|
||||||
return m_cropWidth;
|
void setCropWidth(qreal w);
|
||||||
}
|
|
||||||
void setCropWidth(qreal w);
|
|
||||||
|
|
||||||
qreal cropHeight() const {
|
qreal cropHeight() const { return m_cropHeight; }
|
||||||
return m_cropHeight;
|
void setCropHeight(qreal h);
|
||||||
}
|
|
||||||
void setCropHeight(qreal h);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override;
|
QSGNode* updatePaintNode(
|
||||||
|
QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sourceChanged();
|
void sourceChanged();
|
||||||
void screenResolutionChanged();
|
void screenResolutionChanged();
|
||||||
void zoomChanged();
|
void zoomChanged();
|
||||||
void actualCropChanged();
|
void actualCropChanged();
|
||||||
void cropXChanged();
|
void cropXChanged();
|
||||||
void cropYChanged();
|
void cropYChanged();
|
||||||
void cropWidthChanged();
|
void cropWidthChanged();
|
||||||
void cropHeightChanged();
|
void cropHeightChanged();
|
||||||
void statusChanged();
|
void statusChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void loadImage();
|
void loadImage();
|
||||||
void handleImageLoaded();
|
void handleImageLoaded();
|
||||||
QString getCacheFilePath() const;
|
QString getCacheFilePath() const;
|
||||||
void setStatus(const Status s);
|
void setStatus(const Status s);
|
||||||
|
|
||||||
Status m_status = Null;
|
Status m_status = Null;
|
||||||
QUrl m_source;
|
QUrl m_source;
|
||||||
QSize m_screenResolution;
|
QSize m_screenResolution;
|
||||||
qreal m_zoom = 1.0;
|
qreal m_zoom = 1.0;
|
||||||
|
|
||||||
qreal m_actualCropX = 0.0;
|
qreal m_actualCropX = 0.0;
|
||||||
qreal m_actualCropY = 0.0;
|
qreal m_actualCropY = 0.0;
|
||||||
qreal m_actualCropWidth = 1.0;
|
qreal m_actualCropWidth = 1.0;
|
||||||
qreal m_actualCropHeight = 1.0;
|
qreal m_actualCropHeight = 1.0;
|
||||||
|
|
||||||
qreal m_cropX = 0.0;
|
qreal m_cropX = 0.0;
|
||||||
qreal m_cropY = 0.0;
|
qreal m_cropY = 0.0;
|
||||||
qreal m_cropWidth = 1.0;
|
qreal m_cropWidth = 1.0;
|
||||||
qreal m_cropHeight = 1.0;
|
qreal m_cropHeight = 1.0;
|
||||||
|
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
QSGTexture *m_texture = nullptr;
|
QSGTexture* m_texture = nullptr;
|
||||||
bool m_textureDirty = false;
|
bool m_textureDirty = false;
|
||||||
QFutureWatcher<QImage> m_imageWatcher;
|
QFutureWatcher<QImage> m_imageWatcher;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::internal
|
||||||
|
|||||||
@@ -6,474 +6,512 @@
|
|||||||
|
|
||||||
namespace ZShell::models {
|
namespace ZShell::models {
|
||||||
|
|
||||||
FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent)
|
FileSystemEntry::FileSystemEntry(
|
||||||
: QObject(parent)
|
const QString& path, const QString& relativePath, QObject* parent)
|
||||||
, m_fileInfo(path)
|
: QObject(parent)
|
||||||
, m_path(path)
|
, m_fileInfo(path)
|
||||||
, m_relativePath(relativePath)
|
, m_path(path)
|
||||||
, m_isImageInitialised(false)
|
, m_relativePath(relativePath)
|
||||||
, m_mimeTypeInitialised(false) {}
|
, m_isImageInitialised(false)
|
||||||
|
, m_mimeTypeInitialised(false) {}
|
||||||
|
|
||||||
QString FileSystemEntry::path() const {
|
QString FileSystemEntry::path() const {
|
||||||
return m_path;
|
return m_path;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString FileSystemEntry::relativePath() const {
|
QString FileSystemEntry::relativePath() const {
|
||||||
return m_relativePath;
|
return m_relativePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString FileSystemEntry::name() const {
|
QString FileSystemEntry::name() const {
|
||||||
return m_fileInfo.fileName();
|
return m_fileInfo.fileName();
|
||||||
};
|
};
|
||||||
|
|
||||||
QString FileSystemEntry::baseName() const {
|
QString FileSystemEntry::baseName() const {
|
||||||
return m_fileInfo.baseName();
|
return m_fileInfo.baseName();
|
||||||
};
|
};
|
||||||
|
|
||||||
QString FileSystemEntry::parentDir() const {
|
QString FileSystemEntry::parentDir() const {
|
||||||
return m_fileInfo.absolutePath();
|
return m_fileInfo.absolutePath();
|
||||||
};
|
};
|
||||||
|
|
||||||
QString FileSystemEntry::suffix() const {
|
QString FileSystemEntry::suffix() const {
|
||||||
return m_fileInfo.completeSuffix();
|
return m_fileInfo.completeSuffix();
|
||||||
};
|
};
|
||||||
|
|
||||||
qint64 FileSystemEntry::size() const {
|
qint64 FileSystemEntry::size() const {
|
||||||
return m_fileInfo.size();
|
return m_fileInfo.size();
|
||||||
};
|
};
|
||||||
|
|
||||||
bool FileSystemEntry::isDir() const {
|
bool FileSystemEntry::isDir() const {
|
||||||
return m_fileInfo.isDir();
|
return m_fileInfo.isDir();
|
||||||
};
|
};
|
||||||
|
|
||||||
bool FileSystemEntry::isImage() const {
|
bool FileSystemEntry::isImage() const {
|
||||||
if (!m_isImageInitialised) {
|
if (!m_isImageInitialised) {
|
||||||
QImageReader reader(m_path);
|
QImageReader reader(m_path);
|
||||||
m_isImage = reader.canRead();
|
m_isImage = reader.canRead();
|
||||||
m_isImageInitialised = true;
|
m_isImageInitialised = true;
|
||||||
}
|
}
|
||||||
return m_isImage;
|
return m_isImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FileSystemEntry::mimeType() const {
|
QString FileSystemEntry::mimeType() const {
|
||||||
if (!m_mimeTypeInitialised) {
|
if (!m_mimeTypeInitialised) {
|
||||||
const QMimeDatabase db;
|
const QMimeDatabase db;
|
||||||
m_mimeType = db.mimeTypeForFile(m_path).name();
|
m_mimeType = db.mimeTypeForFile(m_path).name();
|
||||||
m_mimeTypeInitialised = true;
|
m_mimeTypeInitialised = true;
|
||||||
}
|
}
|
||||||
return m_mimeType;
|
return m_mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemEntry::updateRelativePath(const QDir& dir) {
|
void FileSystemEntry::updateRelativePath(const QDir& dir) {
|
||||||
const auto relPath = dir.relativeFilePath(m_path);
|
const auto relPath = dir.relativeFilePath(m_path);
|
||||||
if (m_relativePath != relPath) {
|
if (m_relativePath != relPath) {
|
||||||
m_relativePath = relPath;
|
m_relativePath = relPath;
|
||||||
emit relativePathChanged();
|
emit relativePathChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSystemModel::FileSystemModel(QObject* parent)
|
FileSystemModel::FileSystemModel(QObject* parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_recursive(false)
|
, m_recursive(false)
|
||||||
, m_watchChanges(true)
|
, m_watchChanges(true)
|
||||||
, m_showHidden(false)
|
, m_showHidden(false)
|
||||||
, m_filter(NoFilter) {
|
, m_filter(NoFilter) {
|
||||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
|
connect(
|
||||||
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir);
|
&m_watcher,
|
||||||
|
&QFileSystemWatcher::directoryChanged,
|
||||||
|
this,
|
||||||
|
&FileSystemModel::watchDirIfRecursive);
|
||||||
|
connect(
|
||||||
|
&m_watcher,
|
||||||
|
&QFileSystemWatcher::directoryChanged,
|
||||||
|
this,
|
||||||
|
&FileSystemModel::updateEntriesForDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
int FileSystemModel::rowCount(const QModelIndex& parent) const {
|
int FileSystemModel::rowCount(const QModelIndex& parent) const {
|
||||||
if (parent != QModelIndex()) {
|
if (parent != QModelIndex()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return static_cast<int>(m_entries.size());
|
return static_cast<int>(m_entries.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
|
QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
|
||||||
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) {
|
if (role != Qt::UserRole || !index.isValid() ||
|
||||||
return QVariant();
|
index.row() >= m_entries.size()) {
|
||||||
}
|
return QVariant();
|
||||||
return QVariant::fromValue(m_entries.at(index.row()));
|
}
|
||||||
|
return QVariant::fromValue(m_entries.at(index.row()));
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> FileSystemModel::roleNames() const {
|
QHash<int, QByteArray> FileSystemModel::roleNames() const {
|
||||||
return { { Qt::UserRole, "modelData" } };
|
return {{Qt::UserRole, "modelData"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FileSystemModel::path() const {
|
QString FileSystemModel::path() const {
|
||||||
return m_path;
|
return m_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setPath(const QString& path) {
|
void FileSystemModel::setPath(const QString& path) {
|
||||||
if (m_path == path) {
|
if (m_path == path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_path = path;
|
m_path = path;
|
||||||
emit pathChanged();
|
emit pathChanged();
|
||||||
|
|
||||||
m_dir.setPath(m_path);
|
m_dir.setPath(m_path);
|
||||||
|
|
||||||
for (const auto& entry : std::as_const(m_entries)) {
|
for (const auto& entry : std::as_const(m_entries)) {
|
||||||
entry->updateRelativePath(m_dir);
|
entry->updateRelativePath(m_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSystemModel::recursive() const {
|
bool FileSystemModel::recursive() const {
|
||||||
return m_recursive;
|
return m_recursive;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setRecursive(bool recursive) {
|
void FileSystemModel::setRecursive(bool recursive) {
|
||||||
if (m_recursive == recursive) {
|
if (m_recursive == recursive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_recursive = recursive;
|
m_recursive = recursive;
|
||||||
emit recursiveChanged();
|
emit recursiveChanged();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSystemModel::watchChanges() const {
|
bool FileSystemModel::watchChanges() const {
|
||||||
return m_watchChanges;
|
return m_watchChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setWatchChanges(bool watchChanges) {
|
void FileSystemModel::setWatchChanges(bool watchChanges) {
|
||||||
if (m_watchChanges == watchChanges) {
|
if (m_watchChanges == watchChanges) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_watchChanges = watchChanges;
|
m_watchChanges = watchChanges;
|
||||||
emit watchChangesChanged();
|
emit watchChangesChanged();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSystemModel::showHidden() const {
|
bool FileSystemModel::showHidden() const {
|
||||||
return m_showHidden;
|
return m_showHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setShowHidden(bool showHidden) {
|
void FileSystemModel::setShowHidden(bool showHidden) {
|
||||||
if (m_showHidden == showHidden) {
|
if (m_showHidden == showHidden) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_showHidden = showHidden;
|
m_showHidden = showHidden;
|
||||||
emit showHiddenChanged();
|
emit showHiddenChanged();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSystemModel::sortReverse() const {
|
bool FileSystemModel::sortReverse() const {
|
||||||
return m_sortReverse;
|
return m_sortReverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setSortReverse(bool sortReverse) {
|
void FileSystemModel::setSortReverse(bool sortReverse) {
|
||||||
if (m_sortReverse == sortReverse) {
|
if (m_sortReverse == sortReverse) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sortReverse = sortReverse;
|
m_sortReverse = sortReverse;
|
||||||
emit sortReverseChanged();
|
emit sortReverseChanged();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSystemModel::Filter FileSystemModel::filter() const {
|
FileSystemModel::Filter FileSystemModel::filter() const {
|
||||||
return m_filter;
|
return m_filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setFilter(Filter filter) {
|
void FileSystemModel::setFilter(Filter filter) {
|
||||||
if (m_filter == filter) {
|
if (m_filter == filter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_filter = filter;
|
m_filter = filter;
|
||||||
emit filterChanged();
|
emit filterChanged();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList FileSystemModel::nameFilters() const {
|
QStringList FileSystemModel::nameFilters() const {
|
||||||
return m_nameFilters;
|
return m_nameFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
|
void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
|
||||||
if (m_nameFilters == nameFilters) {
|
if (m_nameFilters == nameFilters) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_nameFilters = nameFilters;
|
m_nameFilters = nameFilters;
|
||||||
emit nameFiltersChanged();
|
emit nameFiltersChanged();
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
|
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
|
||||||
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
|
return QQmlListProperty<FileSystemEntry>(this, &m_entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
void FileSystemModel::watchDirIfRecursive(const QString& path) {
|
||||||
if (m_recursive && m_watchChanges) {
|
if (m_recursive && m_watchChanges) {
|
||||||
const auto currentDir = m_dir;
|
const auto currentDir = m_dir;
|
||||||
const bool showHidden = m_showHidden;
|
const bool showHidden = m_showHidden;
|
||||||
const auto future = QtConcurrent::run([showHidden, path]() {
|
const auto future = QtConcurrent::run([showHidden, path]() {
|
||||||
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||||
if (showHidden) {
|
if (showHidden) {
|
||||||
filters |= QDir::Hidden;
|
filters |= QDir::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDirIterator iter(path, filters, QDirIterator::Subdirectories);
|
QDirIterator iter(path, filters, QDirIterator::Subdirectories);
|
||||||
QStringList dirs;
|
QStringList dirs;
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
dirs << iter.next();
|
dirs << iter.next();
|
||||||
}
|
}
|
||||||
return dirs;
|
return dirs;
|
||||||
});
|
});
|
||||||
const auto watcher = new QFutureWatcher<QStringList>(this);
|
const auto watcher = new QFutureWatcher<QStringList>(this);
|
||||||
connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() {
|
connect(
|
||||||
const auto paths = watcher->result();
|
watcher,
|
||||||
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
|
&QFutureWatcher<QStringList>::finished,
|
||||||
// Ignore if dir or showHidden has changed
|
this,
|
||||||
m_watcher.addPaths(paths);
|
[currentDir, showHidden, watcher, this]() {
|
||||||
}
|
const auto paths = watcher->result();
|
||||||
watcher->deleteLater();
|
if (currentDir == m_dir && showHidden == m_showHidden &&
|
||||||
});
|
!paths.isEmpty()) {
|
||||||
watcher->setFuture(future);
|
// Ignore if dir or showHidden has changed
|
||||||
}
|
m_watcher.addPaths(paths);
|
||||||
|
}
|
||||||
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::update() {
|
void FileSystemModel::update() {
|
||||||
updateWatcher();
|
updateWatcher();
|
||||||
updateEntries();
|
updateEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::updateWatcher() {
|
void FileSystemModel::updateWatcher() {
|
||||||
if (!m_watcher.directories().isEmpty()) {
|
if (!m_watcher.directories().isEmpty()) {
|
||||||
m_watcher.removePaths(m_watcher.directories());
|
m_watcher.removePaths(m_watcher.directories());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_watchChanges || m_path.isEmpty()) {
|
if (!m_watchChanges || m_path.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_watcher.addPath(m_path);
|
m_watcher.addPath(m_path);
|
||||||
watchDirIfRecursive(m_path);
|
watchDirIfRecursive(m_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::updateEntries() {
|
void FileSystemModel::updateEntries() {
|
||||||
if (m_path.isEmpty()) {
|
if (m_path.isEmpty()) {
|
||||||
if (!m_entries.isEmpty()) {
|
if (!m_entries.isEmpty()) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
qDeleteAll(m_entries);
|
qDeleteAll(m_entries);
|
||||||
m_entries.clear();
|
m_entries.clear();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
emit entriesChanged();
|
emit entriesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& future : m_futures) {
|
for (auto& future : m_futures) {
|
||||||
future.cancel();
|
future.cancel();
|
||||||
}
|
}
|
||||||
m_futures.clear();
|
m_futures.clear();
|
||||||
|
|
||||||
updateEntriesForDir(m_path);
|
updateEntriesForDir(m_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
void FileSystemModel::updateEntriesForDir(const QString& dir) {
|
||||||
const auto recursive = m_recursive;
|
const auto recursive = m_recursive;
|
||||||
const auto showHidden = m_showHidden;
|
const auto showHidden = m_showHidden;
|
||||||
const auto filter = m_filter;
|
const auto filter = m_filter;
|
||||||
const auto nameFilters = m_nameFilters;
|
const auto nameFilters = m_nameFilters;
|
||||||
|
|
||||||
QSet<QString> oldPaths;
|
QSet<QString> oldPaths;
|
||||||
for (const auto& entry : std::as_const(m_entries)) {
|
for (const auto& entry : std::as_const(m_entries)) {
|
||||||
oldPaths << entry->path();
|
oldPaths << entry->path();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
const auto future = QtConcurrent::run(
|
||||||
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
|
[=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
|
||||||
|
const auto flags = recursive ? QDirIterator::Subdirectories
|
||||||
|
: QDirIterator::NoIteratorFlags;
|
||||||
|
|
||||||
std::optional<QDirIterator> iter;
|
std::optional<QDirIterator> iter;
|
||||||
|
|
||||||
if (filter == Images) {
|
if (filter == Images) {
|
||||||
QStringList extraNameFilters = nameFilters;
|
QStringList extraNameFilters = nameFilters;
|
||||||
const auto formats = QImageReader::supportedImageFormats();
|
const auto formats = QImageReader::supportedImageFormats();
|
||||||
for (const auto& format : formats) {
|
for (const auto& format : formats) {
|
||||||
extraNameFilters << "*." + format;
|
extraNameFilters << "*." + format;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir::Filters filters = QDir::Files;
|
QDir::Filters filters = QDir::Files;
|
||||||
if (showHidden) {
|
if (showHidden) {
|
||||||
filters |= QDir::Hidden;
|
filters |= QDir::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
iter.emplace(dir, extraNameFilters, filters, flags);
|
iter.emplace(dir, extraNameFilters, filters, flags);
|
||||||
} else {
|
} else {
|
||||||
QDir::Filters filters;
|
QDir::Filters filters;
|
||||||
|
|
||||||
if (filter == Files) {
|
if (filter == Files) {
|
||||||
filters = QDir::Files;
|
filters = QDir::Files;
|
||||||
} else if (filter == Dirs) {
|
} else if (filter == Dirs) {
|
||||||
filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
filters = QDir::Dirs | QDir::NoDotAndDotDot;
|
||||||
} else {
|
} else {
|
||||||
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
|
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showHidden) {
|
if (showHidden) {
|
||||||
filters |= QDir::Hidden;
|
filters |= QDir::Hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nameFilters.isEmpty()) {
|
if (nameFilters.isEmpty()) {
|
||||||
iter.emplace(dir, filters, flags);
|
iter.emplace(dir, filters, flags);
|
||||||
} else {
|
} else {
|
||||||
iter.emplace(dir, nameFilters, filters, flags);
|
iter.emplace(dir, nameFilters, filters, flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<QString> newPaths;
|
QSet<QString> newPaths;
|
||||||
while (iter->hasNext()) {
|
while (iter->hasNext()) {
|
||||||
if (promise.isCanceled()) {
|
if (promise.isCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString path = iter->next();
|
QString path = iter->next();
|
||||||
|
|
||||||
if (filter == Images) {
|
if (filter == Images) {
|
||||||
QImageReader reader(path);
|
QImageReader reader(path);
|
||||||
if (!reader.canRead()) {
|
if (!reader.canRead()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newPaths.insert(path);
|
newPaths.insert(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise.isCanceled() || newPaths == oldPaths) {
|
if (promise.isCanceled() || newPaths == oldPaths) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths));
|
promise.addResult(
|
||||||
});
|
qMakePair(oldPaths - newPaths, newPaths - oldPaths));
|
||||||
|
});
|
||||||
|
|
||||||
if (m_futures.contains(dir)) {
|
if (m_futures.contains(dir)) {
|
||||||
m_futures[dir].cancel();
|
m_futures[dir].cancel();
|
||||||
}
|
}
|
||||||
m_futures.insert(dir, future);
|
m_futures.insert(dir, future);
|
||||||
|
|
||||||
const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
|
const auto watcher =
|
||||||
|
new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
|
||||||
|
|
||||||
connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() {
|
connect(
|
||||||
m_futures.remove(dir);
|
watcher,
|
||||||
|
&QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished,
|
||||||
|
this,
|
||||||
|
[dir, watcher, this]() {
|
||||||
|
m_futures.remove(dir);
|
||||||
|
|
||||||
if (!watcher->future().isResultReadyAt(0)) {
|
if (!watcher->future().isResultReadyAt(0)) {
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto result = watcher->result();
|
const auto result = watcher->result();
|
||||||
applyChanges(result.first, result.second);
|
applyChanges(result.first, result.second);
|
||||||
|
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
|
void FileSystemModel::applyChanges(
|
||||||
QList<int> removedIndices;
|
const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
|
||||||
for (int i = 0; i < m_entries.size(); ++i) {
|
QList<int> removedIndices;
|
||||||
if (removedPaths.contains(m_entries[i]->path())) {
|
for (int i = 0; i < m_entries.size(); ++i) {
|
||||||
removedIndices << i;
|
if (removedPaths.contains(m_entries[i]->path())) {
|
||||||
}
|
removedIndices << i;
|
||||||
}
|
}
|
||||||
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
|
}
|
||||||
|
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
|
||||||
|
|
||||||
// Batch remove old entries
|
// Batch remove old entries
|
||||||
int start = -1;
|
int start = -1;
|
||||||
int end = -1;
|
int end = -1;
|
||||||
for (int idx : std::as_const(removedIndices)) {
|
for (int idx : std::as_const(removedIndices)) {
|
||||||
if (start == -1) {
|
if (start == -1) {
|
||||||
start = idx;
|
start = idx;
|
||||||
end = idx;
|
end = idx;
|
||||||
} else if (idx == end - 1) {
|
} else if (idx == end - 1) {
|
||||||
end = idx;
|
end = idx;
|
||||||
} else {
|
} else {
|
||||||
beginRemoveRows(QModelIndex(), end, start);
|
beginRemoveRows(QModelIndex(), end, start);
|
||||||
for (int i = start; i >= end; --i) {
|
for (int i = start; i >= end; --i) {
|
||||||
m_entries.takeAt(i)->deleteLater();
|
m_entries.takeAt(i)->deleteLater();
|
||||||
}
|
}
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
|
|
||||||
start = idx;
|
start = idx;
|
||||||
end = idx;
|
end = idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (start != -1) {
|
if (start != -1) {
|
||||||
beginRemoveRows(QModelIndex(), end, start);
|
beginRemoveRows(QModelIndex(), end, start);
|
||||||
for (int i = start; i >= end; --i) {
|
for (int i = start; i >= end; --i) {
|
||||||
m_entries.takeAt(i)->deleteLater();
|
m_entries.takeAt(i)->deleteLater();
|
||||||
}
|
}
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new entries
|
// Create new entries
|
||||||
QList<FileSystemEntry*> newEntries;
|
QList<FileSystemEntry*> newEntries;
|
||||||
for (const auto& path : addedPaths) {
|
for (const auto& path : addedPaths) {
|
||||||
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
|
newEntries << new FileSystemEntry(
|
||||||
}
|
path, m_dir.relativeFilePath(path), this);
|
||||||
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
}
|
||||||
return compareEntries(a, b);
|
std::sort(
|
||||||
});
|
newEntries.begin(),
|
||||||
|
newEntries.end(),
|
||||||
|
[this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||||
|
return compareEntries(a, b);
|
||||||
|
});
|
||||||
|
|
||||||
// Batch insert new entries
|
// Batch insert new entries
|
||||||
int insertStart = -1;
|
int insertStart = -1;
|
||||||
QList<FileSystemEntry*> batchItems;
|
QList<FileSystemEntry*> batchItems;
|
||||||
for (const auto& entry : std::as_const(newEntries)) {
|
for (const auto& entry : std::as_const(newEntries)) {
|
||||||
const auto it = std::lower_bound(
|
const auto it = std::lower_bound(
|
||||||
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
m_entries.begin(),
|
||||||
return compareEntries(a, b);
|
m_entries.end(),
|
||||||
});
|
entry,
|
||||||
const auto row = static_cast<int>(it - m_entries.begin());
|
[this](const FileSystemEntry* a, const FileSystemEntry* b) {
|
||||||
|
return compareEntries(a, b);
|
||||||
|
});
|
||||||
|
const auto row = static_cast<int>(it - m_entries.begin());
|
||||||
|
|
||||||
if (insertStart == -1) {
|
if (insertStart == -1) {
|
||||||
insertStart = row;
|
insertStart = row;
|
||||||
batchItems << entry;
|
batchItems << entry;
|
||||||
} else if (row == insertStart + batchItems.size()) {
|
} else if (row == insertStart + batchItems.size()) {
|
||||||
batchItems << entry;
|
batchItems << entry;
|
||||||
} else {
|
} else {
|
||||||
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
|
beginInsertRows(
|
||||||
for (int i = 0; i < batchItems.size(); ++i) {
|
QModelIndex(),
|
||||||
m_entries.insert(insertStart + i, batchItems[i]);
|
insertStart,
|
||||||
}
|
insertStart + static_cast<int>(batchItems.size()) - 1);
|
||||||
endInsertRows();
|
for (int i = 0; i < batchItems.size(); ++i) {
|
||||||
|
m_entries.insert(insertStart + i, batchItems[i]);
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
|
||||||
insertStart = row;
|
insertStart = row;
|
||||||
batchItems.clear();
|
batchItems.clear();
|
||||||
batchItems << entry;
|
batchItems << entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!batchItems.isEmpty()) {
|
if (!batchItems.isEmpty()) {
|
||||||
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
|
beginInsertRows(
|
||||||
for (int i = 0; i < batchItems.size(); ++i) {
|
QModelIndex(),
|
||||||
m_entries.insert(insertStart + i, batchItems[i]);
|
insertStart,
|
||||||
}
|
insertStart + static_cast<int>(batchItems.size()) - 1);
|
||||||
endInsertRows();
|
for (int i = 0; i < batchItems.size(); ++i) {
|
||||||
}
|
m_entries.insert(insertStart + i, batchItems[i]);
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
emit entriesChanged();
|
emit entriesChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const {
|
bool FileSystemModel::compareEntries(
|
||||||
if (a->isDir() != b->isDir()) {
|
const FileSystemEntry* a, const FileSystemEntry* b) const {
|
||||||
return m_sortReverse ^ a->isDir();
|
if (a->isDir() != b->isDir()) {
|
||||||
}
|
return m_sortReverse ^ a->isDir();
|
||||||
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
|
}
|
||||||
return m_sortReverse ? cmp > 0 : cmp < 0;
|
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
|
||||||
|
return m_sortReverse ? cmp > 0 : cmp < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::models
|
} // namespace ZShell::models
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <qabstractitemmodel.h>
|
#include <qabstractitemmodel.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfilesystemwatcher.h>
|
#include <qfilesystemwatcher.h>
|
||||||
@@ -14,136 +13,151 @@
|
|||||||
namespace ZShell::models {
|
namespace ZShell::models {
|
||||||
|
|
||||||
class FileSystemEntry : public QObject {
|
class FileSystemEntry : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
QML_UNCREATABLE(
|
||||||
|
"FileSystemEntry instances can only be retrieved from a "
|
||||||
|
"FileSystemModel")
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path CONSTANT)
|
Q_PROPERTY(QString path READ path CONSTANT)
|
||||||
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
||||||
Q_PROPERTY(QString name READ name CONSTANT)
|
Q_PROPERTY(QString name READ name CONSTANT)
|
||||||
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
||||||
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
||||||
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
||||||
Q_PROPERTY(qint64 size READ size CONSTANT)
|
Q_PROPERTY(qint64 size READ size CONSTANT)
|
||||||
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
||||||
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
||||||
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
|
explicit FileSystemEntry(
|
||||||
|
const QString& path,
|
||||||
|
const QString& relativePath,
|
||||||
|
QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
[[nodiscard]] QString relativePath() const;
|
[[nodiscard]] QString relativePath() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString baseName() const;
|
[[nodiscard]] QString baseName() const;
|
||||||
[[nodiscard]] QString parentDir() const;
|
[[nodiscard]] QString parentDir() const;
|
||||||
[[nodiscard]] QString suffix() const;
|
[[nodiscard]] QString suffix() const;
|
||||||
[[nodiscard]] qint64 size() const;
|
[[nodiscard]] qint64 size() const;
|
||||||
[[nodiscard]] bool isDir() const;
|
[[nodiscard]] bool isDir() const;
|
||||||
[[nodiscard]] bool isImage() const;
|
[[nodiscard]] bool isImage() const;
|
||||||
[[nodiscard]] QString mimeType() const;
|
[[nodiscard]] QString mimeType() const;
|
||||||
|
|
||||||
void updateRelativePath(const QDir& dir);
|
void updateRelativePath(const QDir& dir);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void relativePathChanged();
|
void relativePathChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QFileInfo m_fileInfo;
|
const QFileInfo m_fileInfo;
|
||||||
|
|
||||||
const QString m_path;
|
const QString m_path;
|
||||||
QString m_relativePath;
|
QString m_relativePath;
|
||||||
|
|
||||||
mutable bool m_isImage;
|
mutable bool m_isImage;
|
||||||
mutable bool m_isImageInitialised;
|
mutable bool m_isImageInitialised;
|
||||||
|
|
||||||
mutable QString m_mimeType;
|
mutable QString m_mimeType;
|
||||||
mutable bool m_mimeTypeInitialised;
|
mutable bool m_mimeTypeInitialised;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileSystemModel : public QAbstractListModel {
|
class FileSystemModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
||||||
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY
|
||||||
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
watchChangesChanged)
|
||||||
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
Q_PROPERTY(
|
||||||
|
bool showHidden READ showHidden WRITE setShowHidden NOTIFY
|
||||||
|
showHiddenChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY
|
||||||
|
sortReverseChanged)
|
||||||
|
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY
|
||||||
|
nameFiltersChanged)
|
||||||
|
|
||||||
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
|
Q_PROPERTY(
|
||||||
|
QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries
|
||||||
|
NOTIFY entriesChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Filter {
|
enum Filter { NoFilter, Images, Files, Dirs };
|
||||||
NoFilter,
|
Q_ENUM(Filter)
|
||||||
Images,
|
|
||||||
Files,
|
|
||||||
Dirs
|
|
||||||
};
|
|
||||||
Q_ENUM(Filter)
|
|
||||||
|
|
||||||
explicit FileSystemModel(QObject* parent = nullptr);
|
explicit FileSystemModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
[[nodiscard]] int rowCount(
|
||||||
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
const QModelIndex& parent = QModelIndex()) const override;
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QVariant data(
|
||||||
|
const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
void setPath(const QString& path);
|
void setPath(const QString& path);
|
||||||
|
|
||||||
[[nodiscard]] bool recursive() const;
|
[[nodiscard]] bool recursive() const;
|
||||||
void setRecursive(bool recursive);
|
void setRecursive(bool recursive);
|
||||||
|
|
||||||
[[nodiscard]] bool watchChanges() const;
|
[[nodiscard]] bool watchChanges() const;
|
||||||
void setWatchChanges(bool watchChanges);
|
void setWatchChanges(bool watchChanges);
|
||||||
|
|
||||||
[[nodiscard]] bool showHidden() const;
|
[[nodiscard]] bool showHidden() const;
|
||||||
void setShowHidden(bool showHidden);
|
void setShowHidden(bool showHidden);
|
||||||
|
|
||||||
[[nodiscard]] bool sortReverse() const;
|
[[nodiscard]] bool sortReverse() const;
|
||||||
void setSortReverse(bool sortReverse);
|
void setSortReverse(bool sortReverse);
|
||||||
|
|
||||||
[[nodiscard]] Filter filter() const;
|
[[nodiscard]] Filter filter() const;
|
||||||
void setFilter(Filter filter);
|
void setFilter(Filter filter);
|
||||||
|
|
||||||
[[nodiscard]] QStringList nameFilters() const;
|
[[nodiscard]] QStringList nameFilters() const;
|
||||||
void setNameFilters(const QStringList& nameFilters);
|
void setNameFilters(const QStringList& nameFilters);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void recursiveChanged();
|
void recursiveChanged();
|
||||||
void watchChangesChanged();
|
void watchChangesChanged();
|
||||||
void showHiddenChanged();
|
void showHiddenChanged();
|
||||||
void sortReverseChanged();
|
void sortReverseChanged();
|
||||||
void filterChanged();
|
void filterChanged();
|
||||||
void nameFiltersChanged();
|
void nameFiltersChanged();
|
||||||
void entriesChanged();
|
void entriesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
QFileSystemWatcher m_watcher;
|
QFileSystemWatcher m_watcher;
|
||||||
QList<FileSystemEntry*> m_entries;
|
QList<FileSystemEntry*> m_entries;
|
||||||
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures;
|
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
|
||||||
|
|
||||||
QString m_path;
|
QString m_path;
|
||||||
bool m_recursive;
|
bool m_recursive;
|
||||||
bool m_watchChanges;
|
bool m_watchChanges;
|
||||||
bool m_showHidden;
|
bool m_showHidden;
|
||||||
bool m_sortReverse;
|
bool m_sortReverse;
|
||||||
Filter m_filter;
|
Filter m_filter;
|
||||||
QStringList m_nameFilters;
|
QStringList m_nameFilters;
|
||||||
|
|
||||||
void watchDirIfRecursive(const QString& path);
|
void watchDirIfRecursive(const QString& path);
|
||||||
void update();
|
void update();
|
||||||
void updateWatcher();
|
void updateWatcher();
|
||||||
void updateEntries();
|
void updateEntries();
|
||||||
void updateEntriesForDir(const QString& dir);
|
void updateEntriesForDir(const QString& dir);
|
||||||
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
void applyChanges(
|
||||||
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||||
|
[[nodiscard]] bool compareEntries(
|
||||||
|
const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::models
|
} // namespace ZShell::models
|
||||||
|
|||||||
@@ -23,20 +23,33 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
|
|||||||
|
|
||||||
m_loop = pw_main_loop_new(nullptr);
|
m_loop = pw_main_loop_new(nullptr);
|
||||||
if (!m_loop) {
|
if (!m_loop) {
|
||||||
qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop";
|
qWarning()
|
||||||
|
<< "PipeWireWorker::init: failed to create PipeWire main loop";
|
||||||
pw_deinit();
|
pw_deinit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
|
timespec timeout = {0, 10 * SPA_NSEC_PER_MSEC};
|
||||||
m_timer = pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this);
|
m_timer =
|
||||||
pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
|
pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this);
|
||||||
|
pw_loop_update_timer(
|
||||||
|
pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
|
||||||
|
|
||||||
auto props = pw_properties_new(
|
auto props = pw_properties_new(
|
||||||
PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr);
|
PW_KEY_MEDIA_TYPE,
|
||||||
|
"Audio",
|
||||||
|
PW_KEY_MEDIA_CATEGORY,
|
||||||
|
"Capture",
|
||||||
|
PW_KEY_MEDIA_ROLE,
|
||||||
|
"Music",
|
||||||
|
nullptr);
|
||||||
pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
|
pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
|
||||||
pw_properties_setf(
|
pw_properties_setf(
|
||||||
props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * ac::SAMPLE_RATE / 48000), ac::SAMPLE_RATE);
|
props,
|
||||||
|
PW_KEY_NODE_LATENCY,
|
||||||
|
"%u/%u",
|
||||||
|
nextPowerOf2(512 * ac::SAMPLE_RATE / 48000),
|
||||||
|
ac::SAMPLE_RATE);
|
||||||
pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true");
|
pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true");
|
||||||
pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
|
pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
|
||||||
pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false");
|
pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false");
|
||||||
@@ -55,21 +68,28 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
|
|||||||
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
|
params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
|
||||||
|
|
||||||
pw_stream_events events{};
|
pw_stream_events events{};
|
||||||
events.state_changed = [](void* data, pw_stream_state, pw_stream_state state, const char*) {
|
events.state_changed =
|
||||||
auto* self = static_cast<PipeWireWorker*>(data);
|
[](void* data, pw_stream_state, pw_stream_state state, const char*) {
|
||||||
self->streamStateChanged(state);
|
auto* self = static_cast<PipeWireWorker*>(data);
|
||||||
};
|
self->streamStateChanged(state);
|
||||||
|
};
|
||||||
events.process = [](void* data) {
|
events.process = [](void* data) {
|
||||||
auto* self = static_cast<PipeWireWorker*>(data);
|
auto* self = static_cast<PipeWireWorker*>(data);
|
||||||
self->processStream();
|
self->processStream();
|
||||||
};
|
};
|
||||||
|
|
||||||
m_stream = pw_stream_new_simple(pw_main_loop_get_loop(m_loop), "ZShell-shell", props, &events, this);
|
m_stream = pw_stream_new_simple(
|
||||||
|
pw_main_loop_get_loop(m_loop), "ZShell-shell", props, &events, this);
|
||||||
|
|
||||||
const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, PW_ID_ANY,
|
const int success = pw_stream_connect(
|
||||||
static_cast<pw_stream_flags>(
|
m_stream,
|
||||||
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
|
PW_DIRECTION_INPUT,
|
||||||
params, 1);
|
PW_ID_ANY,
|
||||||
|
static_cast<pw_stream_flags>(
|
||||||
|
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
|
||||||
|
PW_STREAM_FLAG_RT_PROCESS),
|
||||||
|
params,
|
||||||
|
1);
|
||||||
if (success < 0) {
|
if (success < 0) {
|
||||||
qWarning() << "PipeWireWorker::init: failed to connect stream";
|
qWarning() << "PipeWireWorker::init: failed to connect stream";
|
||||||
pw_stream_destroy(m_stream);
|
pw_stream_destroy(m_stream);
|
||||||
@@ -98,8 +118,13 @@ void PipeWireWorker::handleTimeout(void* data, uint64_t expirations) {
|
|||||||
self->m_collector->clearBuffer();
|
self->m_collector->clearBuffer();
|
||||||
} else {
|
} else {
|
||||||
self->m_idle = true;
|
self->m_idle = true;
|
||||||
timespec timeout = { 0, 500 * SPA_NSEC_PER_MSEC };
|
timespec timeout = {0, 500 * SPA_NSEC_PER_MSEC};
|
||||||
pw_loop_update_timer(pw_main_loop_get_loop(self->m_loop), self->m_timer, &timeout, &timeout, false);
|
pw_loop_update_timer(
|
||||||
|
pw_main_loop_get_loop(self->m_loop),
|
||||||
|
self->m_timer,
|
||||||
|
&timeout,
|
||||||
|
&timeout,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,12 +133,14 @@ void PipeWireWorker::streamStateChanged(pw_stream_state state) {
|
|||||||
m_idle = false;
|
m_idle = false;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case PW_STREAM_STATE_PAUSED: {
|
case PW_STREAM_STATE_PAUSED: {
|
||||||
timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
|
timespec timeout = {0, 10 * SPA_NSEC_PER_MSEC};
|
||||||
pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
|
pw_loop_update_timer(
|
||||||
|
pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PW_STREAM_STATE_STREAMING:
|
case PW_STREAM_STATE_STREAMING:
|
||||||
pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false);
|
pw_loop_update_timer(
|
||||||
|
pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false);
|
||||||
break;
|
break;
|
||||||
case PW_STREAM_STATE_ERROR:
|
case PW_STREAM_STATE_ERROR:
|
||||||
pw_main_loop_quit(m_loop);
|
pw_main_loop_quit(m_loop);
|
||||||
@@ -171,7 +198,8 @@ void AudioCollector::clearBuffer() {
|
|||||||
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
||||||
std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
|
std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
|
||||||
|
|
||||||
auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
|
auto* oldRead =
|
||||||
|
m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
|
||||||
m_writeBuffer.store(oldRead, std::memory_order_release);
|
m_writeBuffer.store(oldRead, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,11 +209,13 @@ void AudioCollector::loadChunk(const qint16* samples, quint32 count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
|
||||||
std::transform(samples, samples + count, writeBuffer->begin(), [](qint16 sample) {
|
std::transform(
|
||||||
|
samples, samples + count, writeBuffer->begin(), [](qint16 sample) {
|
||||||
return sample / 32768.0f;
|
return sample / 32768.0f;
|
||||||
});
|
});
|
||||||
|
|
||||||
auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
|
auto* oldRead =
|
||||||
|
m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
|
||||||
m_writeBuffer.store(oldRead, std::memory_order_release);
|
m_writeBuffer.store(oldRead, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,9 +236,11 @@ quint32 AudioCollector::readChunk(double* out, quint32 count) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
|
auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
|
||||||
std::transform(readBuffer->begin(), readBuffer->begin() + count, out, [](float sample) {
|
std::transform(
|
||||||
return static_cast<double>(sample);
|
readBuffer->begin(),
|
||||||
});
|
readBuffer->begin() + count,
|
||||||
|
out,
|
||||||
|
[](float sample) { return static_cast<double>(sample); });
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@@ -218,8 +250,7 @@ AudioCollector::AudioCollector(QObject* parent)
|
|||||||
, m_buffer1(ac::CHUNK_SIZE)
|
, m_buffer1(ac::CHUNK_SIZE)
|
||||||
, m_buffer2(ac::CHUNK_SIZE)
|
, m_buffer2(ac::CHUNK_SIZE)
|
||||||
, m_readBuffer(&m_buffer1)
|
, m_readBuffer(&m_buffer1)
|
||||||
, m_writeBuffer(&m_buffer2) {
|
, m_writeBuffer(&m_buffer2) {}
|
||||||
}
|
|
||||||
|
|
||||||
AudioCollector::~AudioCollector() {
|
AudioCollector::~AudioCollector() {
|
||||||
stop();
|
stop();
|
||||||
@@ -232,9 +263,8 @@ void AudioCollector::start() {
|
|||||||
|
|
||||||
clearBuffer();
|
clearBuffer();
|
||||||
|
|
||||||
m_thread = std::jthread([this](std::stop_token token) {
|
m_thread = std::jthread(
|
||||||
PipeWireWorker worker(token, this);
|
[this](std::stop_token token) { PipeWireWorker worker(token, this); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioCollector::stop() {
|
void AudioCollector::stop() {
|
||||||
|
|||||||
@@ -22,55 +22,55 @@ constexpr quint32 CHUNK_SIZE = 512;
|
|||||||
class AudioCollector;
|
class AudioCollector;
|
||||||
|
|
||||||
class PipeWireWorker {
|
class PipeWireWorker {
|
||||||
public:
|
public:
|
||||||
explicit PipeWireWorker(std::stop_token token, AudioCollector* collector);
|
explicit PipeWireWorker(std::stop_token token, AudioCollector* collector);
|
||||||
|
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
pw_main_loop* m_loop;
|
pw_main_loop* m_loop;
|
||||||
pw_stream* m_stream;
|
pw_stream* m_stream;
|
||||||
spa_source* m_timer;
|
spa_source* m_timer;
|
||||||
bool m_idle;
|
bool m_idle;
|
||||||
|
|
||||||
std::stop_token m_token;
|
std::stop_token m_token;
|
||||||
AudioCollector* m_collector;
|
AudioCollector* m_collector;
|
||||||
|
|
||||||
static void handleTimeout(void* data, uint64_t expirations);
|
static void handleTimeout(void* data, uint64_t expirations);
|
||||||
void streamStateChanged(pw_stream_state state);
|
void streamStateChanged(pw_stream_state state);
|
||||||
void processStream();
|
void processStream();
|
||||||
|
|
||||||
[[nodiscard]] unsigned int nextPowerOf2(unsigned int n);
|
[[nodiscard]] unsigned int nextPowerOf2(unsigned int n);
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioCollector : public Service {
|
class AudioCollector : public Service {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AudioCollector(const AudioCollector&) = delete;
|
AudioCollector(const AudioCollector&) = delete;
|
||||||
AudioCollector& operator=(const AudioCollector&) = delete;
|
AudioCollector& operator=(const AudioCollector&) = delete;
|
||||||
|
|
||||||
static AudioCollector& instance();
|
static AudioCollector& instance();
|
||||||
|
|
||||||
void clearBuffer();
|
void clearBuffer();
|
||||||
void loadChunk(const qint16* samples, quint32 count);
|
void loadChunk(const qint16* samples, quint32 count);
|
||||||
quint32 readChunk(float* out, quint32 count = 0);
|
quint32 readChunk(float* out, quint32 count = 0);
|
||||||
quint32 readChunk(double* out, quint32 count = 0);
|
quint32 readChunk(double* out, quint32 count = 0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit AudioCollector(QObject* parent = nullptr);
|
explicit AudioCollector(QObject* parent = nullptr);
|
||||||
~AudioCollector() override;
|
~AudioCollector() override;
|
||||||
|
|
||||||
std::jthread m_thread;
|
std::jthread m_thread;
|
||||||
std::vector<float> m_buffer1;
|
std::vector<float> m_buffer1;
|
||||||
std::vector<float> m_buffer2;
|
std::vector<float> m_buffer2;
|
||||||
std::atomic<std::vector<float>*> m_readBuffer;
|
std::atomic<std::vector<float>*> m_readBuffer;
|
||||||
std::atomic<std::vector<float>*> m_writeBuffer;
|
std::atomic<std::vector<float>*> m_writeBuffer;
|
||||||
quint32 m_sampleCount;
|
quint32 m_sampleCount;
|
||||||
|
|
||||||
void reload();
|
void reload();
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
|
|
||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
AudioProcessor::AudioProcessor(QObject* parent)
|
AudioProcessor::AudioProcessor(QObject* parent) : QObject(parent) {}
|
||||||
: QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioProcessor::~AudioProcessor() {
|
AudioProcessor::~AudioProcessor() {
|
||||||
stop();
|
stop();
|
||||||
@@ -17,12 +15,17 @@ AudioProcessor::~AudioProcessor() {
|
|||||||
|
|
||||||
void AudioProcessor::init() {
|
void AudioProcessor::init() {
|
||||||
m_timer = new QTimer(this);
|
m_timer = new QTimer(this);
|
||||||
m_timer->setInterval(static_cast<int>(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE));
|
m_timer->setInterval(
|
||||||
|
static_cast<int>(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE));
|
||||||
connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
|
connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioProcessor::start() {
|
void AudioProcessor::start() {
|
||||||
QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::ref, Qt::QueuedConnection, this);
|
QMetaObject::invokeMethod(
|
||||||
|
&AudioCollector::instance(),
|
||||||
|
&AudioCollector::ref,
|
||||||
|
Qt::QueuedConnection,
|
||||||
|
this);
|
||||||
if (m_timer) {
|
if (m_timer) {
|
||||||
m_timer->start();
|
m_timer->start();
|
||||||
}
|
}
|
||||||
@@ -32,14 +35,15 @@ void AudioProcessor::stop() {
|
|||||||
if (m_timer) {
|
if (m_timer) {
|
||||||
m_timer->stop();
|
m_timer->stop();
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::unref, Qt::QueuedConnection, this);
|
QMetaObject::invokeMethod(
|
||||||
|
&AudioCollector::instance(),
|
||||||
|
&AudioCollector::unref,
|
||||||
|
Qt::QueuedConnection,
|
||||||
|
this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioProvider::AudioProvider(QObject* parent)
|
AudioProvider::AudioProvider(QObject* parent)
|
||||||
: Service(parent)
|
: Service(parent), m_processor(nullptr), m_thread(nullptr) {}
|
||||||
, m_processor(nullptr)
|
|
||||||
, m_thread(nullptr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioProvider::~AudioProvider() {
|
AudioProvider::~AudioProvider() {
|
||||||
if (m_thread) {
|
if (m_thread) {
|
||||||
@@ -50,7 +54,8 @@ AudioProvider::~AudioProvider() {
|
|||||||
|
|
||||||
void AudioProvider::init() {
|
void AudioProvider::init() {
|
||||||
if (!m_processor) {
|
if (!m_processor) {
|
||||||
qWarning() << "AudioProvider::init: attempted to init with no processor set";
|
qWarning()
|
||||||
|
<< "AudioProvider::init: attempted to init with no processor set";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +63,8 @@ void AudioProvider::init() {
|
|||||||
m_processor->moveToThread(m_thread);
|
m_processor->moveToThread(m_thread);
|
||||||
|
|
||||||
connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init);
|
connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init);
|
||||||
connect(m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater);
|
connect(
|
||||||
|
m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater);
|
||||||
connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater);
|
connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater);
|
||||||
|
|
||||||
m_thread->start();
|
m_thread->start();
|
||||||
|
|||||||
@@ -7,41 +7,41 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class AudioProcessor : public QObject {
|
class AudioProcessor : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AudioProcessor(QObject* parent = nullptr);
|
explicit AudioProcessor(QObject* parent = nullptr);
|
||||||
~AudioProcessor() override;
|
~AudioProcessor() override;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void process() = 0;
|
virtual void process() = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTimer* m_timer;
|
QTimer* m_timer;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioProvider : public Service {
|
class AudioProvider : public Service {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AudioProvider(QObject* parent = nullptr);
|
explicit AudioProvider(QObject* parent = nullptr);
|
||||||
~AudioProvider() override;
|
~AudioProvider() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
AudioProcessor* m_processor;
|
AudioProcessor* m_processor;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QThread* m_thread;
|
QThread* m_thread;
|
||||||
|
|
||||||
void start() override;
|
void start() override;
|
||||||
void stop() override;
|
void stop() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ BeatProcessor::BeatProcessor(QObject* parent)
|
|||||||
: AudioProcessor(parent)
|
: AudioProcessor(parent)
|
||||||
, m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE))
|
, m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE))
|
||||||
, m_in(new_fvec(ac::CHUNK_SIZE))
|
, m_in(new_fvec(ac::CHUNK_SIZE))
|
||||||
, m_out(new_fvec(2)) {
|
, m_out(new_fvec(2)) {};
|
||||||
};
|
|
||||||
|
|
||||||
BeatProcessor::~BeatProcessor() {
|
BeatProcessor::~BeatProcessor() {
|
||||||
if (m_tempo) {
|
if (m_tempo) {
|
||||||
@@ -36,13 +35,15 @@ void BeatProcessor::process() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BeatTracker::BeatTracker(QObject* parent)
|
BeatTracker::BeatTracker(QObject* parent) : AudioProvider(parent), m_bpm(120) {
|
||||||
: AudioProvider(parent)
|
|
||||||
, m_bpm(120) {
|
|
||||||
m_processor = new BeatProcessor();
|
m_processor = new BeatProcessor();
|
||||||
init();
|
init();
|
||||||
|
|
||||||
connect(static_cast<BeatProcessor*>(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm);
|
connect(
|
||||||
|
static_cast<BeatProcessor*>(m_processor),
|
||||||
|
&BeatProcessor::beat,
|
||||||
|
this,
|
||||||
|
&BeatTracker::updateBpm);
|
||||||
}
|
}
|
||||||
|
|
||||||
smpl_t BeatTracker::bpm() const {
|
smpl_t BeatTracker::bpm() const {
|
||||||
|
|||||||
@@ -7,43 +7,43 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class BeatProcessor : public AudioProcessor {
|
class BeatProcessor : public AudioProcessor {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BeatProcessor(QObject* parent = nullptr);
|
explicit BeatProcessor(QObject* parent = nullptr);
|
||||||
~BeatProcessor() override;
|
~BeatProcessor() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void beat(smpl_t bpm);
|
void beat(smpl_t bpm);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process() override;
|
void process() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
aubio_tempo_t* m_tempo;
|
aubio_tempo_t* m_tempo;
|
||||||
fvec_t* m_in;
|
fvec_t* m_in;
|
||||||
fvec_t* m_out;
|
fvec_t* m_out;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BeatTracker : public AudioProvider {
|
class BeatTracker : public AudioProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
|
Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BeatTracker(QObject* parent = nullptr);
|
explicit BeatTracker(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] smpl_t bpm() const;
|
[[nodiscard]] smpl_t bpm() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void bpmChanged();
|
void bpmChanged();
|
||||||
void beat(smpl_t bpm);
|
void beat(smpl_t bpm);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
smpl_t m_bpm;
|
smpl_t m_bpm;
|
||||||
|
|
||||||
void updateBpm(smpl_t bpm);
|
void updateBpm(smpl_t bpm);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "audiocollector.hpp"
|
#include "audiocollector.hpp"
|
||||||
#include "audioprovider.hpp"
|
#include "audioprovider.hpp"
|
||||||
#include <algorithm>
|
|
||||||
#include <cava/cavacore.h>
|
#include <cava/cavacore.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <qdebug.h>
|
#include <qdebug.h>
|
||||||
@@ -14,8 +13,7 @@ CavaProcessor::CavaProcessor(QObject* parent)
|
|||||||
, m_plan(nullptr)
|
, m_plan(nullptr)
|
||||||
, m_in(new double[ac::CHUNK_SIZE])
|
, m_in(new double[ac::CHUNK_SIZE])
|
||||||
, m_out(nullptr)
|
, m_out(nullptr)
|
||||||
, m_bars(0) {
|
, m_bars(0) {};
|
||||||
};
|
|
||||||
|
|
||||||
CavaProcessor::~CavaProcessor() {
|
CavaProcessor::~CavaProcessor() {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -27,7 +25,8 @@ void CavaProcessor::process() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int count = static_cast<int>(AudioCollector::instance().readChunk(m_in));
|
const int count =
|
||||||
|
static_cast<int>(AudioCollector::instance().readChunk(m_in));
|
||||||
|
|
||||||
// Process in data via cava
|
// Process in data via cava
|
||||||
cava_execute(m_in, count, m_out, m_plan);
|
cava_execute(m_in, count, m_out, m_plan);
|
||||||
@@ -35,7 +34,7 @@ void CavaProcessor::process() {
|
|||||||
// Apply monstercat filter
|
// Apply monstercat filter
|
||||||
QVector<double> values(m_bars);
|
QVector<double> values(m_bars);
|
||||||
|
|
||||||
for(int i = 0; i < m_bars; ++i) {
|
for (int i = 0; i < m_bars; ++i) {
|
||||||
values[i] = m_out[i];
|
values[i] = m_out[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +62,8 @@ void CavaProcessor::process() {
|
|||||||
|
|
||||||
void CavaProcessor::setBars(int bars) {
|
void CavaProcessor::setBars(int bars) {
|
||||||
if (bars < 0) {
|
if (bars < 0) {
|
||||||
qWarning() << "CavaProcessor::setBars: bars must be greater than 0. Setting to 0.";
|
qWarning() << "CavaProcessor::setBars: bars must be greater than 0. "
|
||||||
|
"Setting to 0.";
|
||||||
bars = 0;
|
bars = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +100,15 @@ void CavaProcessor::initCava() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CavaProvider::CavaProvider(QObject* parent)
|
CavaProvider::CavaProvider(QObject* parent)
|
||||||
: AudioProvider(parent)
|
: AudioProvider(parent), m_bars(0), m_values(m_bars, 0.0) {
|
||||||
, m_bars(0)
|
|
||||||
, m_values(m_bars, 0.0) {
|
|
||||||
m_processor = new CavaProcessor();
|
m_processor = new CavaProcessor();
|
||||||
init();
|
init();
|
||||||
|
|
||||||
connect(static_cast<CavaProcessor*>(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues);
|
connect(
|
||||||
|
static_cast<CavaProcessor*>(m_processor),
|
||||||
|
&CavaProcessor::valuesChanged,
|
||||||
|
this,
|
||||||
|
&CavaProvider::updateValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
int CavaProvider::bars() const {
|
int CavaProvider::bars() const {
|
||||||
@@ -115,7 +117,8 @@ int CavaProvider::bars() const {
|
|||||||
|
|
||||||
void CavaProvider::setBars(int bars) {
|
void CavaProvider::setBars(int bars) {
|
||||||
if (bars < 0) {
|
if (bars < 0) {
|
||||||
qWarning() << "CavaProvider::setBars: bars must be greater than 0. Setting to 0.";
|
qWarning() << "CavaProvider::setBars: bars must be greater than 0. "
|
||||||
|
"Setting to 0.";
|
||||||
bars = 0;
|
bars = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +132,10 @@ void CavaProvider::setBars(int bars) {
|
|||||||
emit valuesChanged();
|
emit valuesChanged();
|
||||||
|
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
static_cast<CavaProcessor*>(m_processor), &CavaProcessor::setBars, Qt::QueuedConnection, bars);
|
static_cast<CavaProcessor*>(m_processor),
|
||||||
|
&CavaProcessor::setBars,
|
||||||
|
Qt::QueuedConnection,
|
||||||
|
bars);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<double> CavaProvider::values() const {
|
QVector<double> CavaProvider::values() const {
|
||||||
|
|||||||
@@ -7,58 +7,58 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class CavaProcessor : public AudioProcessor {
|
class CavaProcessor : public AudioProcessor {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CavaProcessor(QObject* parent = nullptr);
|
explicit CavaProcessor(QObject* parent = nullptr);
|
||||||
~CavaProcessor() override;
|
~CavaProcessor() override;
|
||||||
|
|
||||||
void setBars(int bars);
|
void setBars(int bars);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void valuesChanged(QVector<double> values);
|
void valuesChanged(QVector<double> values);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void process() override;
|
void process() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct cava_plan* m_plan;
|
struct cava_plan* m_plan;
|
||||||
double* m_in;
|
double* m_in;
|
||||||
double* m_out;
|
double* m_out;
|
||||||
|
|
||||||
int m_bars;
|
int m_bars;
|
||||||
QVector<double> m_values;
|
QVector<double> m_values;
|
||||||
|
|
||||||
void reload();
|
void reload();
|
||||||
void initCava();
|
void initCava();
|
||||||
void cleanup();
|
void cleanup();
|
||||||
};
|
};
|
||||||
|
|
||||||
class CavaProvider : public AudioProvider {
|
class CavaProvider : public AudioProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged)
|
Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged)
|
||||||
|
|
||||||
Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged)
|
Q_PROPERTY(QVector<double> values READ values NOTIFY valuesChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CavaProvider(QObject* parent = nullptr);
|
explicit CavaProvider(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] int bars() const;
|
[[nodiscard]] int bars() const;
|
||||||
void setBars(int bars);
|
void setBars(int bars);
|
||||||
|
|
||||||
[[nodiscard]] QVector<double> values() const;
|
[[nodiscard]] QVector<double> values() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void barsChanged();
|
void barsChanged();
|
||||||
void valuesChanged();
|
void valuesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_bars;
|
int m_bars;
|
||||||
QVector<double> m_values;
|
QVector<double> m_values;
|
||||||
|
|
||||||
void updateValues(QVector<double> values);
|
void updateValues(QVector<double> values);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
|
|
||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
Cpu::Cpu(QObject* parent)
|
Cpu::Cpu(QObject* parent) : TickingService(parent) {
|
||||||
: TickingService(parent) {
|
|
||||||
readNameOnce();
|
readNameOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +40,8 @@ void Cpu::readNameOnce() {
|
|||||||
const QByteArray data = f.readAll();
|
const QByteArray data = f.readAll();
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
static const QRegularExpression re(QStringLiteral("model name\\s*:\\s*(.+)"));
|
static const QRegularExpression re(
|
||||||
|
QStringLiteral("model name\\s*:\\s*(.+)"));
|
||||||
const auto match = re.match(QString::fromLatin1(data));
|
const auto match = re.match(QString::fromLatin1(data));
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
return;
|
return;
|
||||||
@@ -64,8 +64,9 @@ void Cpu::refreshPercentage() {
|
|||||||
const QByteArray data = f.readAll();
|
const QByteArray data = f.readAll();
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
static const QRegularExpression re(
|
static const QRegularExpression re(QStringLiteral(
|
||||||
QStringLiteral("^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"));
|
"^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)"
|
||||||
|
"\\s+(\\d+)\\s+(\\d+)"));
|
||||||
const auto match = re.match(QString::fromLatin1(data));
|
const auto match = re.match(QString::fromLatin1(data));
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
return;
|
return;
|
||||||
@@ -83,7 +84,10 @@ void Cpu::refreshPercentage() {
|
|||||||
|
|
||||||
const quint64 totalDiff = total > m_lastTotal ? total - m_lastTotal : 0;
|
const quint64 totalDiff = total > m_lastTotal ? total - m_lastTotal : 0;
|
||||||
const quint64 idleDiff = idle > m_lastIdle ? idle - m_lastIdle : 0;
|
const quint64 idleDiff = idle > m_lastIdle ? idle - m_lastIdle : 0;
|
||||||
const qreal newPerc = totalDiff > 0 ? 1.0 - static_cast<qreal>(idleDiff) / static_cast<qreal>(totalDiff) : 0.0;
|
const qreal newPerc =
|
||||||
|
totalDiff > 0
|
||||||
|
? 1.0 - static_cast<qreal>(idleDiff) / static_cast<qreal>(totalDiff)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
m_lastTotal = total;
|
m_lastTotal = total;
|
||||||
m_lastIdle = idle;
|
m_lastIdle = idle;
|
||||||
@@ -105,7 +109,8 @@ void Cpu::refreshTemperature() {
|
|||||||
|
|
||||||
QString Cpu::cleanName(QString s) {
|
QString Cpu::cleanName(QString s) {
|
||||||
static const QRegularExpression noise(
|
static const QRegularExpression noise(
|
||||||
QStringLiteral("\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"),
|
QStringLiteral(
|
||||||
|
"\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"),
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
static const QRegularExpression spaces(QStringLiteral("\\s+"));
|
static const QRegularExpression spaces(QStringLiteral("\\s+"));
|
||||||
|
|
||||||
|
|||||||
@@ -7,42 +7,42 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class Cpu : public TickingService {
|
class Cpu : public TickingService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
||||||
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
|
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Cpu(QObject* parent = nullptr);
|
explicit Cpu(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] qreal percentage() const;
|
[[nodiscard]] qreal percentage() const;
|
||||||
[[nodiscard]] qreal temperature() const;
|
[[nodiscard]] qreal temperature() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void percentageChanged();
|
void percentageChanged();
|
||||||
void temperatureChanged();
|
void temperatureChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void tick() override;
|
void tick() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void readNameOnce();
|
void readNameOnce();
|
||||||
void refreshPercentage();
|
void refreshPercentage();
|
||||||
void refreshTemperature();
|
void refreshTemperature();
|
||||||
|
|
||||||
[[nodiscard]] static QString cleanName(QString s);
|
[[nodiscard]] static QString cleanName(QString s);
|
||||||
|
|
||||||
QString m_name;
|
QString m_name;
|
||||||
qreal m_percentage = 0.0;
|
qreal m_percentage = 0.0;
|
||||||
qreal m_temperature = 0.0;
|
qreal m_temperature = 0.0;
|
||||||
quint64 m_lastIdle = 0;
|
quint64 m_lastIdle = 0;
|
||||||
quint64 m_lastTotal = 0;
|
quint64 m_lastTotal = 0;
|
||||||
bool m_nameLoaded = false;
|
bool m_nameLoaded = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -17,49 +17,57 @@ struct DesktopItem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class DesktopModel : public QAbstractListModel {
|
class DesktopModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum DesktopRoles {
|
enum DesktopRoles {
|
||||||
FileNameRole = Qt::UserRole + 1,
|
FileNameRole = Qt::UserRole + 1,
|
||||||
FilePathRole,
|
FilePathRole,
|
||||||
IsDirRole,
|
IsDirRole,
|
||||||
GridXRole,
|
GridXRole,
|
||||||
GridYRole
|
GridYRole
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit DesktopModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] int rowCount(
|
||||||
|
const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
[[nodiscard]] QVariant data(
|
||||||
|
const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
Q_INVOKABLE void loadDirectory(const QString& path);
|
||||||
|
Q_INVOKABLE void moveIcon(int index, int newX, int newY);
|
||||||
|
Q_INVOKABLE void massMove(
|
||||||
|
const QVariantList& selectedPathsList,
|
||||||
|
const QString& leaderPath,
|
||||||
|
int targetX,
|
||||||
|
int targetY,
|
||||||
|
int maxCol,
|
||||||
|
int maxRow);
|
||||||
|
|
||||||
|
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
|
||||||
|
|
||||||
|
[[nodiscard]] int rows() const { return m_rows; }
|
||||||
|
void setRows(int r) {
|
||||||
|
if (m_rows != r) {
|
||||||
|
m_rows = r;
|
||||||
|
emit rowsChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void rowsChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_rows = 1;
|
||||||
|
QList<DesktopItem> m_items;
|
||||||
|
QString m_watchedPath;
|
||||||
|
QFileSystemWatcher m_watcher;
|
||||||
|
void saveCurrentLayout();
|
||||||
|
[[nodiscard]] QPoint getEmptySpot(const QSet<QString>& occupied) const;
|
||||||
|
void onDirectoryChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit DesktopModel(QObject *parent = nullptr);
|
}; // namespace ZShell::services
|
||||||
|
|
||||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
Q_INVOKABLE void loadDirectory(const QString &path);
|
|
||||||
Q_INVOKABLE void moveIcon(int index, int newX, int newY);
|
|
||||||
Q_INVOKABLE void massMove(const QVariantList &selectedPathsList, const QString &leaderPath, int targetX, int targetY, int maxCol, int maxRow);
|
|
||||||
|
|
||||||
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
[[nodiscard]] int rows() const {
|
|
||||||
return m_rows;
|
|
||||||
}
|
|
||||||
void setRows(int r) {
|
|
||||||
if (m_rows != r) { m_rows = r; emit rowsChanged(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void rowsChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_rows = 1;
|
|
||||||
QList<DesktopItem> m_items;
|
|
||||||
QString m_watchedPath;
|
|
||||||
QFileSystemWatcher m_watcher;
|
|
||||||
void saveCurrentLayout();
|
|
||||||
[[nodiscard]] QPoint getEmptySpot(const QSet<QString> &occupied) const;
|
|
||||||
void onDirectoryChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -9,11 +9,12 @@
|
|||||||
|
|
||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
DesktopStateManager::DesktopStateManager(QObject *parent) : QObject(parent) {
|
DesktopStateManager::DesktopStateManager(QObject* parent) : QObject(parent) {}
|
||||||
}
|
|
||||||
|
|
||||||
QString DesktopStateManager::getConfigFilePath() const {
|
QString DesktopStateManager::getConfigFilePath() const {
|
||||||
QString configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/zshell";
|
QString configDir =
|
||||||
|
QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
|
||||||
|
"/zshell";
|
||||||
QDir dir(configDir);
|
QDir dir(configDir);
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
dir.mkpath(".");
|
dir.mkpath(".");
|
||||||
@@ -30,7 +31,8 @@ void DesktopStateManager::saveLayout(const QVariantMap& layout) {
|
|||||||
file.write(doc.toJson(QJsonDocument::Indented));
|
file.write(doc.toJson(QJsonDocument::Indented));
|
||||||
file.close();
|
file.close();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "zshell: Cannot save desktop layout to" << getConfigFilePath();
|
qWarning() << "zshell: Cannot save desktop layout to"
|
||||||
|
<< getConfigFilePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,18 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class DesktopStateManager : public QObject {
|
class DesktopStateManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DesktopStateManager(QObject *parent = nullptr);
|
explicit DesktopStateManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
Q_INVOKABLE void saveLayout(const QVariantMap& layout);
|
Q_INVOKABLE void saveLayout(const QVariantMap& layout);
|
||||||
Q_INVOKABLE QVariantMap getLayout();
|
Q_INVOKABLE QVariantMap getLayout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] QString getConfigFilePath() const;
|
[[nodiscard]] QString getConfigFilePath() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -8,13 +8,17 @@ constexpr qreal kKib = 1024.0;
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
DiskInfo::DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent)
|
DiskInfo::DiskInfo(
|
||||||
|
QString mount,
|
||||||
|
quint64 usedBytes,
|
||||||
|
quint64 totalBytes,
|
||||||
|
bool hasRoot,
|
||||||
|
QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_mount(std::move(mount))
|
, m_mount(std::move(mount))
|
||||||
, m_usedBytes(usedBytes)
|
, m_usedBytes(usedBytes)
|
||||||
, m_totalBytes(totalBytes)
|
, m_totalBytes(totalBytes)
|
||||||
, m_hasRoot(hasRoot) {
|
, m_hasRoot(hasRoot) {}
|
||||||
}
|
|
||||||
|
|
||||||
QString DiskInfo::mount() const {
|
QString DiskInfo::mount() const {
|
||||||
return m_mount;
|
return m_mount;
|
||||||
@@ -29,12 +33,15 @@ qreal DiskInfo::total() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qreal DiskInfo::free() const {
|
qreal DiskInfo::free() const {
|
||||||
const quint64 freeBytes = m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0;
|
const quint64 freeBytes =
|
||||||
|
m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0;
|
||||||
return static_cast<qreal>(freeBytes) / kKib;
|
return static_cast<qreal>(freeBytes) / kKib;
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal DiskInfo::perc() const {
|
qreal DiskInfo::perc() const {
|
||||||
return m_totalBytes > 0 ? static_cast<qreal>(m_usedBytes) / static_cast<qreal>(m_totalBytes) : 0.0;
|
return m_totalBytes > 0 ? static_cast<qreal>(m_usedBytes) /
|
||||||
|
static_cast<qreal>(m_totalBytes)
|
||||||
|
: 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DiskInfo::hasRoot() const {
|
bool DiskInfo::hasRoot() const {
|
||||||
|
|||||||
@@ -6,41 +6,46 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class DiskInfo : public QObject {
|
class DiskInfo : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("DiskInfo is created by DiskUsage")
|
QML_UNCREATABLE("DiskInfo is created by DiskUsage")
|
||||||
|
|
||||||
Q_PROPERTY(QString mount READ mount CONSTANT)
|
Q_PROPERTY(QString mount READ mount CONSTANT)
|
||||||
Q_PROPERTY(qreal used READ used NOTIFY usedChanged)
|
Q_PROPERTY(qreal used READ used NOTIFY usedChanged)
|
||||||
Q_PROPERTY(qreal total READ total NOTIFY totalChanged)
|
Q_PROPERTY(qreal total READ total NOTIFY totalChanged)
|
||||||
Q_PROPERTY(qreal free READ free NOTIFY freeChanged)
|
Q_PROPERTY(qreal free READ free NOTIFY freeChanged)
|
||||||
Q_PROPERTY(qreal perc READ perc NOTIFY percChanged)
|
Q_PROPERTY(qreal perc READ perc NOTIFY percChanged)
|
||||||
Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged)
|
Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent = nullptr);
|
DiskInfo(
|
||||||
|
QString mount,
|
||||||
|
quint64 usedBytes,
|
||||||
|
quint64 totalBytes,
|
||||||
|
bool hasRoot,
|
||||||
|
QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QString mount() const;
|
[[nodiscard]] QString mount() const;
|
||||||
[[nodiscard]] qreal used() const;
|
[[nodiscard]] qreal used() const;
|
||||||
[[nodiscard]] qreal total() const;
|
[[nodiscard]] qreal total() const;
|
||||||
[[nodiscard]] qreal free() const;
|
[[nodiscard]] qreal free() const;
|
||||||
[[nodiscard]] qreal perc() const;
|
[[nodiscard]] qreal perc() const;
|
||||||
[[nodiscard]] bool hasRoot() const;
|
[[nodiscard]] bool hasRoot() const;
|
||||||
|
|
||||||
void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot);
|
void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void usedChanged();
|
void usedChanged();
|
||||||
void totalChanged();
|
void totalChanged();
|
||||||
void freeChanged();
|
void freeChanged();
|
||||||
void percChanged();
|
void percChanged();
|
||||||
void hasRootChanged();
|
void hasRootChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_mount;
|
QString m_mount;
|
||||||
quint64 m_usedBytes;
|
quint64 m_usedBytes;
|
||||||
quint64 m_totalBytes;
|
quint64 m_totalBytes;
|
||||||
bool m_hasRoot;
|
bool m_hasRoot;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -8,81 +8,81 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class Gpu : public TickingService {
|
class Gpu : public TickingService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Type {
|
enum Type {
|
||||||
Auto, // user override is empty (config "") — defer to detected autoType
|
Auto, // user override is empty (config "") — defer to detected autoType
|
||||||
None, // no usable GPU
|
None, // no usable GPU
|
||||||
Nvidia, // queried via nvidia-smi
|
Nvidia, // queried via nvidia-smi
|
||||||
Generic, // queried via /sys/class/drm/card*/device/gpu_busy_percent
|
Generic, // queried via /sys/class/drm/card*/device/gpu_busy_percent
|
||||||
};
|
};
|
||||||
Q_ENUM(Type)
|
Q_ENUM(Type)
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Q_PROPERTY(Type type READ type NOTIFY typeChanged)
|
Q_PROPERTY(Type type READ type NOTIFY typeChanged)
|
||||||
Q_PROPERTY(Type userType READ userType NOTIFY userTypeChanged)
|
Q_PROPERTY(Type userType READ userType NOTIFY userTypeChanged)
|
||||||
Q_PROPERTY(Type autoType READ autoType NOTIFY autoTypeChanged)
|
Q_PROPERTY(Type autoType READ autoType NOTIFY autoTypeChanged)
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
||||||
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
|
Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged)
|
||||||
Q_PROPERTY(qreal memoryUsed READ memoryUsed NOTIFY memoryUsedChanged)
|
Q_PROPERTY(qreal memoryUsed READ memoryUsed NOTIFY memoryUsedChanged)
|
||||||
Q_PROPERTY(qreal memoryTotal READ memoryTotal NOTIFY memoryTotalChanged)
|
Q_PROPERTY(qreal memoryTotal READ memoryTotal NOTIFY memoryTotalChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Gpu(QObject* parent = nullptr);
|
explicit Gpu(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] Type type() const;
|
[[nodiscard]] Type type() const;
|
||||||
[[nodiscard]] Type userType() const;
|
[[nodiscard]] Type userType() const;
|
||||||
[[nodiscard]] Type autoType() const;
|
[[nodiscard]] Type autoType() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] qreal percentage() const;
|
[[nodiscard]] qreal percentage() const;
|
||||||
[[nodiscard]] qreal temperature() const;
|
[[nodiscard]] qreal temperature() const;
|
||||||
[[nodiscard]] qreal memoryUsed() const;
|
[[nodiscard]] qreal memoryUsed() const;
|
||||||
[[nodiscard]] qreal memoryTotal() const;
|
[[nodiscard]] qreal memoryTotal() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void typeChanged();
|
void typeChanged();
|
||||||
void userTypeChanged();
|
void userTypeChanged();
|
||||||
void autoTypeChanged();
|
void autoTypeChanged();
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void percentageChanged();
|
void percentageChanged();
|
||||||
void temperatureChanged();
|
void temperatureChanged();
|
||||||
void memoryUsedChanged();
|
void memoryUsedChanged();
|
||||||
void memoryTotalChanged();
|
void memoryTotalChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void tick() override;
|
void tick() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void detectTypeOnce();
|
void detectTypeOnce();
|
||||||
void detectNameOnce();
|
void detectNameOnce();
|
||||||
void readGenericUsage();
|
void readGenericUsage();
|
||||||
void startNvidiaUsage();
|
void startNvidiaUsage();
|
||||||
void readGpuTemperature();
|
void readGpuTemperature();
|
||||||
|
|
||||||
void setUserType(Type value);
|
void setUserType(Type value);
|
||||||
void setAutoType(Type value);
|
void setAutoType(Type value);
|
||||||
void setName(QString value);
|
void setName(QString value);
|
||||||
void setMemoryUsed(qreal value);
|
void setMemoryUsed(qreal value);
|
||||||
void setMemoryTotal(qreal value);
|
void setMemoryTotal(qreal value);
|
||||||
|
|
||||||
[[nodiscard]] static Type parseType(const QString& s);
|
[[nodiscard]] static Type parseType(const QString& s);
|
||||||
[[nodiscard]] static QString cleanName(QString s);
|
[[nodiscard]] static QString cleanName(QString s);
|
||||||
|
|
||||||
Type m_userType = Auto;
|
Type m_userType = Auto;
|
||||||
Type m_autoType = None;
|
Type m_autoType = None;
|
||||||
QString m_name;
|
QString m_name;
|
||||||
qreal m_percentage = 0.0;
|
qreal m_percentage = 0.0;
|
||||||
qreal m_temperature = 0.0;
|
qreal m_temperature = 0.0;
|
||||||
qreal m_memoryUsed = 0.0;
|
qreal m_memoryUsed = 0.0;
|
||||||
qreal m_memoryTotal = 0.0;
|
qreal m_memoryTotal = 0.0;
|
||||||
|
|
||||||
QProcess* m_typeProc = nullptr;
|
QProcess* m_typeProc = nullptr;
|
||||||
QProcess* m_nameProc = nullptr;
|
QProcess* m_nameProc = nullptr;
|
||||||
QProcess* m_nvidiaProc = nullptr;
|
QProcess* m_nvidiaProc = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -9,14 +9,14 @@ namespace ZShell::services {
|
|||||||
HyprsunsetManager::HyprsunsetManager(QObject* parent) : QObject(parent) {
|
HyprsunsetManager::HyprsunsetManager(QObject* parent) : QObject(parent) {
|
||||||
connect(&m_timer, &QTimer::timeout, this, &HyprsunsetManager::apply);
|
connect(&m_timer, &QTimer::timeout, this, &HyprsunsetManager::apply);
|
||||||
connect(&m_manualTimer, &QTimer::timeout, this, [this] {
|
connect(&m_manualTimer, &QTimer::timeout, this, [this] {
|
||||||
m_manualToggle = false;
|
m_manualToggle = false;
|
||||||
emit manualToggleChanged();
|
emit manualToggleChanged();
|
||||||
apply();
|
apply();
|
||||||
});
|
});
|
||||||
connect(&m_startCooldown, &QTimer::timeout, this, [this] {
|
connect(&m_startCooldown, &QTimer::timeout, this, [this] {
|
||||||
m_startAllowed = true;
|
m_startAllowed = true;
|
||||||
apply();
|
apply();
|
||||||
});
|
});
|
||||||
|
|
||||||
m_startCooldown.start(2000);
|
m_startCooldown.start(2000);
|
||||||
m_manualTimer.setSingleShot(true);
|
m_manualTimer.setSingleShot(true);
|
||||||
@@ -51,16 +51,14 @@ int HyprsunsetManager::temp() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::setActiveAuto(bool activeAuto) {
|
void HyprsunsetManager::setActiveAuto(bool activeAuto) {
|
||||||
if (activeAuto == m_activeAuto)
|
if (activeAuto == m_activeAuto) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_activeAuto = activeAuto;
|
m_activeAuto = activeAuto;
|
||||||
emit activeAutoChanged();
|
emit activeAutoChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::setManualToggle(bool toggle) {
|
void HyprsunsetManager::setManualToggle(bool toggle) {
|
||||||
if (toggle == m_manualToggle)
|
if (toggle == m_manualToggle) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_manualToggle = toggle;
|
m_manualToggle = toggle;
|
||||||
emit manualToggleChanged();
|
emit manualToggleChanged();
|
||||||
@@ -69,8 +67,7 @@ void HyprsunsetManager::setManualToggle(bool toggle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::setEndTime(const int& time) {
|
void HyprsunsetManager::setEndTime(const int& time) {
|
||||||
if (time == m_endTime)
|
if (time == m_endTime) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_endTime = time;
|
m_endTime = time;
|
||||||
emit endTimeChanged();
|
emit endTimeChanged();
|
||||||
@@ -78,8 +75,7 @@ void HyprsunsetManager::setEndTime(const int& time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::setStartTime(const int& time) {
|
void HyprsunsetManager::setStartTime(const int& time) {
|
||||||
if (time == m_startTime)
|
if (time == m_startTime) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_startTime = time;
|
m_startTime = time;
|
||||||
emit startTimeChanged();
|
emit startTimeChanged();
|
||||||
@@ -87,8 +83,7 @@ void HyprsunsetManager::setStartTime(const int& time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::setTemp(const int& temp) {
|
void HyprsunsetManager::setTemp(const int& temp) {
|
||||||
if (temp == m_temp)
|
if (temp == m_temp) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_temp = temp;
|
m_temp = temp;
|
||||||
emit tempChanged();
|
emit tempChanged();
|
||||||
@@ -104,8 +99,7 @@ void HyprsunsetManager::toggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::start() {
|
void HyprsunsetManager::start() {
|
||||||
if (m_enabled && m_initialized)
|
if (m_enabled && m_initialized) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
m_enabled = true;
|
m_enabled = true;
|
||||||
@@ -113,13 +107,13 @@ void HyprsunsetManager::start() {
|
|||||||
emit enabledChanged();
|
emit enabledChanged();
|
||||||
|
|
||||||
m_process.setProgram("hyprctl");
|
m_process.setProgram("hyprctl");
|
||||||
m_process.setArguments({"hyprsunset", "temperature", QString::number(m_temp)});
|
m_process.setArguments(
|
||||||
|
{"hyprsunset", "temperature", QString::number(m_temp)});
|
||||||
m_process.startDetached();
|
m_process.startDetached();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::end() {
|
void HyprsunsetManager::end() {
|
||||||
if (!m_enabled && m_initialized)
|
if (!m_enabled && m_initialized) return;
|
||||||
return;
|
|
||||||
|
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
m_enabled = false;
|
m_enabled = false;
|
||||||
@@ -132,12 +126,11 @@ void HyprsunsetManager::end() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HyprsunsetManager::apply() {
|
void HyprsunsetManager::apply() {
|
||||||
if (m_manualToggle || !m_activeAuto || !m_startAllowed)
|
if (m_manualToggle || !m_activeAuto || !m_startAllowed) return;
|
||||||
return;
|
|
||||||
|
|
||||||
const auto current = QTime::currentTime();
|
const auto current = QTime::currentTime();
|
||||||
const auto currentMin = current.hour() * 60 + current.minute();
|
const auto currentMin = current.hour() * 60 + current.minute();
|
||||||
bool isDarkTime;
|
bool isDarkTime = false;
|
||||||
|
|
||||||
if (m_startTime <= m_endTime) {
|
if (m_startTime <= m_endTime) {
|
||||||
isDarkTime = (currentMin >= m_startTime && currentMin < m_endTime);
|
isDarkTime = (currentMin >= m_startTime && currentMin < m_endTime);
|
||||||
@@ -152,4 +145,4 @@ void HyprsunsetManager::apply() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
}; // namespace ZShell::services
|
||||||
|
|||||||
@@ -10,57 +10,62 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class HyprsunsetManager : public QObject {
|
class HyprsunsetManager : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
|
Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
|
||||||
Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(int endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged)
|
||||||
Q_PROPERTY(int temp READ temp WRITE setTemp NOTIFY tempChanged)
|
Q_PROPERTY(int endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged)
|
||||||
Q_PROPERTY(bool activeAuto READ activeAuto WRITE setActiveAuto NOTIFY activeAutoChanged)
|
Q_PROPERTY(int temp READ temp WRITE setTemp NOTIFY tempChanged)
|
||||||
Q_PROPERTY(bool manualToggle READ manualToggle WRITE setManualToggle NOTIFY manualToggleChanged)
|
Q_PROPERTY(
|
||||||
|
bool activeAuto READ activeAuto WRITE setActiveAuto NOTIFY
|
||||||
|
activeAutoChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
bool manualToggle READ manualToggle WRITE setManualToggle NOTIFY
|
||||||
|
manualToggleChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HyprsunsetManager(QObject* parent = nullptr);
|
explicit HyprsunsetManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] int startTime() const;
|
[[nodiscard]] int startTime() const;
|
||||||
[[nodiscard]] int endTime() const;
|
[[nodiscard]] int endTime() const;
|
||||||
[[nodiscard]] bool enabled() const;
|
[[nodiscard]] bool enabled() const;
|
||||||
[[nodiscard]] int temp() const;
|
[[nodiscard]] int temp() const;
|
||||||
[[nodiscard]] bool activeAuto() const;
|
[[nodiscard]] bool activeAuto() const;
|
||||||
[[nodiscard]] bool manualToggle() const;
|
[[nodiscard]] bool manualToggle() const;
|
||||||
|
|
||||||
Q_INVOKABLE void toggle();
|
Q_INVOKABLE void toggle();
|
||||||
Q_INVOKABLE void apply();
|
Q_INVOKABLE void apply();
|
||||||
|
|
||||||
void setStartTime(const int& time);
|
void setStartTime(const int& time);
|
||||||
void setEndTime(const int& time);
|
void setEndTime(const int& time);
|
||||||
void setTemp(const int& temp);
|
void setTemp(const int& temp);
|
||||||
void setActiveAuto(bool activeAuto);
|
void setActiveAuto(bool activeAuto);
|
||||||
void setManualToggle(bool toggle);
|
void setManualToggle(bool toggle);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void enabledChanged();
|
void enabledChanged();
|
||||||
void startTimeChanged();
|
void startTimeChanged();
|
||||||
void activeAutoChanged();
|
void activeAutoChanged();
|
||||||
void endTimeChanged();
|
void endTimeChanged();
|
||||||
void tempChanged();
|
void tempChanged();
|
||||||
void manualToggleChanged();
|
void manualToggleChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_startTime;
|
int m_startTime;
|
||||||
int m_endTime;
|
int m_endTime;
|
||||||
bool m_enabled = false;
|
bool m_enabled = false;
|
||||||
bool m_manualToggle = false;
|
bool m_manualToggle = false;
|
||||||
bool m_activeAuto;
|
bool m_activeAuto;
|
||||||
bool m_startAllowed = false;
|
bool m_startAllowed = false;
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
QTimer m_startCooldown;
|
QTimer m_startCooldown;
|
||||||
int m_temp;
|
int m_temp;
|
||||||
QProcess m_process;
|
QProcess m_process;
|
||||||
QTimer m_timer;
|
QTimer m_timer;
|
||||||
QTimer m_manualTimer;
|
QTimer m_manualTimer;
|
||||||
void start();
|
void start();
|
||||||
void end();
|
void end();
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
}; // namespace ZShell::services
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
Memory::Memory(QObject* parent)
|
Memory::Memory(QObject* parent) : TickingService(parent) {}
|
||||||
: TickingService(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal Memory::used() const {
|
qreal Memory::used() const {
|
||||||
return m_used;
|
return m_used;
|
||||||
@@ -29,8 +27,10 @@ void Memory::tick() {
|
|||||||
const QByteArray data = f.readAll();
|
const QByteArray data = f.readAll();
|
||||||
f.close();
|
f.close();
|
||||||
|
|
||||||
static const QRegularExpression reTotal(QStringLiteral("MemTotal: *(\\d+)"));
|
static const QRegularExpression reTotal(
|
||||||
static const QRegularExpression reAvail(QStringLiteral("MemAvailable: *(\\d+)"));
|
QStringLiteral("MemTotal: *(\\d+)"));
|
||||||
|
static const QRegularExpression reAvail(
|
||||||
|
QStringLiteral("MemAvailable: *(\\d+)"));
|
||||||
const QString text = QString::fromLatin1(data);
|
const QString text = QString::fromLatin1(data);
|
||||||
|
|
||||||
const auto totalMatch = reTotal.match(text);
|
const auto totalMatch = reTotal.match(text);
|
||||||
|
|||||||
@@ -8,32 +8,32 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class Memory : public TickingService {
|
class Memory : public TickingService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
Q_PROPERTY(qreal used READ used NOTIFY changed)
|
Q_PROPERTY(qreal used READ used NOTIFY changed)
|
||||||
Q_PROPERTY(qreal total READ total NOTIFY changed)
|
Q_PROPERTY(qreal total READ total NOTIFY changed)
|
||||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY changed)
|
Q_PROPERTY(qreal percentage READ percentage NOTIFY changed)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Memory(QObject* parent = nullptr);
|
explicit Memory(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] qreal used() const;
|
[[nodiscard]] qreal used() const;
|
||||||
[[nodiscard]] qreal total() const;
|
[[nodiscard]] qreal total() const;
|
||||||
[[nodiscard]] qreal percentage() const;
|
[[nodiscard]] qreal percentage() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void changed();
|
void changed();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void tick() override;
|
void tick() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qreal m_used = 0.0;
|
qreal m_used = 0.0;
|
||||||
qreal m_total = 1.0;
|
qreal m_total = 1.0;
|
||||||
quint64 m_lastUsed = 0;
|
quint64 m_lastUsed = 0;
|
||||||
quint64 m_lastTotal = 0;
|
quint64 m_lastTotal = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace ZShell::services::sensorslib {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::atomic<bool> g_initOk{ false };
|
std::atomic<bool> g_initOk{false};
|
||||||
std::once_flag g_initFlag;
|
std::once_flag g_initFlag;
|
||||||
|
|
||||||
void doInit() {
|
void doInit() {
|
||||||
@@ -25,14 +25,16 @@ void doInit() {
|
|||||||
}
|
}
|
||||||
g_initOk.store(true, std::memory_order_release);
|
g_initOk.store(true, std::memory_order_release);
|
||||||
std::atexit([] {
|
std::atexit([] {
|
||||||
if (g_initOk.load(std::memory_order_acquire)) {
|
if (g_initOk.load(std::memory_order_acquire)) {
|
||||||
sensors_cleanup();
|
sensors_cleanup();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::optional<double> readTempInput(const sensors_chip_name* chip, const sensors_feature* feat) {
|
[[nodiscard]] std::optional<double> readTempInput(
|
||||||
const sensors_subfeature* sf = sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT);
|
const sensors_chip_name* chip, const sensors_feature* feat) {
|
||||||
|
const sensors_subfeature* sf =
|
||||||
|
sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT);
|
||||||
if (!sf) {
|
if (!sf) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -43,7 +45,8 @@ void doInit() {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QByteArray featureLabel(const sensors_chip_name* chip, const sensors_feature* feat) {
|
[[nodiscard]] QByteArray featureLabel(
|
||||||
|
const sensors_chip_name* chip, const sensors_feature* feat) {
|
||||||
char* raw = sensors_get_label(chip, feat);
|
char* raw = sensors_get_label(chip, feat);
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
return {};
|
return {};
|
||||||
@@ -59,7 +62,8 @@ bool labelEquals(const QByteArray& label, const char* literal) {
|
|||||||
|
|
||||||
bool labelStartsWith(const QByteArray& label, const char* prefix) {
|
bool labelStartsWith(const QByteArray& label, const char* prefix) {
|
||||||
const auto n = std::strlen(prefix);
|
const auto n = std::strlen(prefix);
|
||||||
return static_cast<size_t>(label.size()) >= n && std::memcmp(label.constData(), prefix, n) == 0;
|
return static_cast<size_t>(label.size()) >= n &&
|
||||||
|
std::memcmp(label.constData(), prefix, n) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -74,13 +78,15 @@ std::optional<double> cpuPackageTemp() {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<double> primary; // Package id N / Tdie
|
std::optional<double> primary; // Package id N / Tdie
|
||||||
std::optional<double> fallback; // Tctl
|
std::optional<double> fallback; // Tctl
|
||||||
|
|
||||||
int chipNr = 0;
|
int chipNr = 0;
|
||||||
while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) {
|
while (const sensors_chip_name* chip =
|
||||||
|
sensors_get_detected_chips(nullptr, &chipNr)) {
|
||||||
int featNr = 0;
|
int featNr = 0;
|
||||||
while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) {
|
while (const sensors_feature* feat =
|
||||||
|
sensors_get_features(chip, &featNr)) {
|
||||||
if (feat->type != SENSORS_FEATURE_TEMP) {
|
if (feat->type != SENSORS_FEATURE_TEMP) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -89,7 +95,8 @@ std::optional<double> cpuPackageTemp() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (labelStartsWith(label, "Package id ") || labelEquals(label, "Tdie")) {
|
if (labelStartsWith(label, "Package id ") ||
|
||||||
|
labelEquals(label, "Tdie")) {
|
||||||
if (auto v = readTempInput(chip, feat)) {
|
if (auto v = readTempInput(chip, feat)) {
|
||||||
primary = v;
|
primary = v;
|
||||||
}
|
}
|
||||||
@@ -116,13 +123,15 @@ std::optional<double> gpuPciAverageTemp() {
|
|||||||
int countFallback = 0;
|
int countFallback = 0;
|
||||||
|
|
||||||
int chipNr = 0;
|
int chipNr = 0;
|
||||||
while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) {
|
while (const sensors_chip_name* chip =
|
||||||
|
sensors_get_detected_chips(nullptr, &chipNr)) {
|
||||||
if (chip->bus.type != SENSORS_BUS_TYPE_PCI) {
|
if (chip->bus.type != SENSORS_BUS_TYPE_PCI) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int featNr = 0;
|
int featNr = 0;
|
||||||
while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) {
|
while (const sensors_feature* feat =
|
||||||
|
sensors_get_features(chip, &featNr)) {
|
||||||
if (feat->type != SENSORS_FEATURE_TEMP) {
|
if (feat->type != SENSORS_FEATURE_TEMP) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -131,10 +140,14 @@ std::optional<double> gpuPciAverageTemp() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool tempIndexed = labelStartsWith(label, "temp") && label.size() > 4 &&
|
const bool tempIndexed =
|
||||||
std::isdigit(static_cast<unsigned char>(label[4]));
|
labelStartsWith(label, "temp") && label.size() > 4 &&
|
||||||
const bool isPrimary = tempIndexed || labelEquals(label, "GPU core") || labelEquals(label, "edge");
|
std::isdigit(static_cast<unsigned char>(label[4]));
|
||||||
const bool isFallback = labelEquals(label, "junction") || labelEquals(label, "mem");
|
const bool isPrimary = tempIndexed ||
|
||||||
|
labelEquals(label, "GPU core") ||
|
||||||
|
labelEquals(label, "edge");
|
||||||
|
const bool isFallback = labelEquals(label, "junction") ||
|
||||||
|
labelEquals(label, "mem");
|
||||||
|
|
||||||
if (!isPrimary && !isFallback) {
|
if (!isPrimary && !isFallback) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
Service::Service(QObject* parent)
|
Service::Service(QObject* parent) : QObject(parent) {}
|
||||||
: QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Service::ref(QObject* sender) {
|
void Service::ref(QObject* sender) {
|
||||||
if (m_refs.isEmpty()) {
|
if (m_refs.isEmpty()) {
|
||||||
|
|||||||
@@ -6,19 +6,19 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class Service : public QObject {
|
class Service : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Service(QObject* parent = nullptr);
|
explicit Service(QObject* parent = nullptr);
|
||||||
|
|
||||||
void ref(QObject* sender);
|
void ref(QObject* sender);
|
||||||
void unref(QObject* sender);
|
void unref(QObject* sender);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSet<QObject*> m_refs;
|
QSet<QObject*> m_refs;
|
||||||
|
|
||||||
virtual void start() = 0;
|
virtual void start() = 0;
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
ServiceRef::ServiceRef(Service* service, QObject* parent)
|
ServiceRef::ServiceRef(Service* service, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent), m_service(service) {
|
||||||
, m_service(service) {
|
|
||||||
if (m_service) {
|
if (m_service) {
|
||||||
m_service->ref(this);
|
m_service->ref(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,24 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class ServiceRef : public QObject {
|
class ServiceRef : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(ZShell::services::Service* service READ service WRITE setService NOTIFY serviceChanged)
|
Q_PROPERTY(
|
||||||
|
ZShell::services::Service* service READ service WRITE setService NOTIFY
|
||||||
|
serviceChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr);
|
explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] Service* service() const;
|
[[nodiscard]] Service* service() const;
|
||||||
void setService(Service* service);
|
void setService(Service* service);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void serviceChanged();
|
void serviceChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<Service> m_service;
|
QPointer<Service> m_service;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ struct Accum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] QString sysfsRealPath(uint major, uint minor) {
|
[[nodiscard]] QString sysfsRealPath(uint major, uint minor) {
|
||||||
const QString link = QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor);
|
const QString link =
|
||||||
|
QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor);
|
||||||
const QString resolved = QFileInfo(link).canonicalFilePath();
|
const QString resolved = QFileInfo(link).canonicalFilePath();
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool readDevtFromSysfs(const QString& sysfsBlockDir, uint& major, uint& minor) {
|
[[nodiscard]] bool readDevtFromSysfs(
|
||||||
|
const QString& sysfsBlockDir, uint& major, uint& minor) {
|
||||||
QFile f(sysfsBlockDir + QStringLiteral("/dev"));
|
QFile f(sysfsBlockDir + QStringLiteral("/dev"));
|
||||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -50,6 +52,7 @@ struct Accum {
|
|||||||
|
|
||||||
QStringList resolveByDevt(uint major, uint minor, int depth = 0);
|
QStringList resolveByDevt(uint major, uint minor, int depth = 0);
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(misc-no-recursion)
|
||||||
QStringList resolveAtNode(const QString& node, int depth) {
|
QStringList resolveAtNode(const QString& node, int depth) {
|
||||||
if (node.isEmpty() || depth > 8) {
|
if (node.isEmpty() || depth > 8) {
|
||||||
return {};
|
return {};
|
||||||
@@ -62,18 +65,20 @@ QStringList resolveAtNode(const QString& node, int depth) {
|
|||||||
|
|
||||||
if (QFileInfo::exists(node + QStringLiteral("/partition"))) {
|
if (QFileInfo::exists(node + QStringLiteral("/partition"))) {
|
||||||
const QString diskNode = nodeInfo.path();
|
const QString diskNode = nodeInfo.path();
|
||||||
return { QFileInfo(diskNode).fileName() };
|
return {QFileInfo(diskNode).fileName()};
|
||||||
}
|
}
|
||||||
|
|
||||||
const QDir slavesDir(node + QStringLiteral("/slaves"));
|
const QDir slavesDir(node + QStringLiteral("/slaves"));
|
||||||
if (slavesDir.exists()) {
|
if (slavesDir.exists()) {
|
||||||
const QStringList slaves = slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
const QStringList slaves =
|
||||||
|
slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
if (!slaves.isEmpty()) {
|
if (!slaves.isEmpty()) {
|
||||||
QStringList out;
|
QStringList out;
|
||||||
for (const QString& slave : slaves) {
|
for (const QString& slave : slaves) {
|
||||||
uint sm = 0;
|
uint sm = 0;
|
||||||
uint sn = 0;
|
uint sn = 0;
|
||||||
const QString slaveDir = QStringLiteral("/sys/class/block/") + slave;
|
const QString slaveDir =
|
||||||
|
QStringLiteral("/sys/class/block/") + slave;
|
||||||
if (!readDevtFromSysfs(slaveDir, sm, sn)) {
|
if (!readDevtFromSysfs(slaveDir, sm, sn)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -88,18 +93,17 @@ QStringList resolveAtNode(const QString& node, int depth) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { nodeInfo.fileName() };
|
return {nodeInfo.fileName()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(misc-no-recursion)
|
||||||
QStringList resolveByDevt(uint major, uint minor, int depth) {
|
QStringList resolveByDevt(uint major, uint minor, int depth) {
|
||||||
return resolveAtNode(sysfsRealPath(major, minor), depth);
|
return resolveAtNode(sysfsRealPath(major, minor), depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Storage::Storage(QObject* parent)
|
Storage::Storage(QObject* parent) : TickingService(parent) {}
|
||||||
: TickingService(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
qreal Storage::percentage() const {
|
qreal Storage::percentage() const {
|
||||||
qreal totalUsed = 0.0;
|
qreal totalUsed = 0.0;
|
||||||
@@ -124,7 +128,8 @@ bool Storage::sameOrder(const QList<DiskInfo*>& a, const QList<DiskInfo*>& b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QQmlListProperty<DiskInfo> Storage::disksProp() {
|
QQmlListProperty<DiskInfo> Storage::disksProp() {
|
||||||
return QQmlListProperty<DiskInfo>(this, nullptr, &Storage::disksCount, &Storage::disksAt);
|
return QQmlListProperty<DiskInfo>(
|
||||||
|
this, nullptr, &Storage::disksCount, &Storage::disksAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
qsizetype Storage::disksCount(QQmlListProperty<DiskInfo>* prop) {
|
qsizetype Storage::disksCount(QQmlListProperty<DiskInfo>* prop) {
|
||||||
@@ -157,30 +162,11 @@ DiskInfo* Storage::primaryDisk() const {
|
|||||||
|
|
||||||
bool Storage::isPseudoFs(QByteArrayView fsType) {
|
bool Storage::isPseudoFs(QByteArrayView fsType) {
|
||||||
static constexpr const char* kPseudo[] = {
|
static constexpr const char* kPseudo[] = {
|
||||||
"tmpfs",
|
"tmpfs", "devtmpfs", "proc", "sysfs", "cgroup",
|
||||||
"devtmpfs",
|
"cgroup2", "overlay", "squashfs", "devpts", "mqueue",
|
||||||
"proc",
|
"ramfs", "rpc_pipefs", "autofs", "configfs", "debugfs",
|
||||||
"sysfs",
|
"tracefs", "securityfs", "pstore", "bpf", "binfmt_misc",
|
||||||
"cgroup",
|
"hugetlbfs", "fusectl", "efivarfs", "selinuxfs",
|
||||||
"cgroup2",
|
|
||||||
"overlay",
|
|
||||||
"squashfs",
|
|
||||||
"devpts",
|
|
||||||
"mqueue",
|
|
||||||
"ramfs",
|
|
||||||
"rpc_pipefs",
|
|
||||||
"autofs",
|
|
||||||
"configfs",
|
|
||||||
"debugfs",
|
|
||||||
"tracefs",
|
|
||||||
"securityfs",
|
|
||||||
"pstore",
|
|
||||||
"bpf",
|
|
||||||
"binfmt_misc",
|
|
||||||
"hugetlbfs",
|
|
||||||
"fusectl",
|
|
||||||
"efivarfs",
|
|
||||||
"selinuxfs",
|
|
||||||
};
|
};
|
||||||
for (const char* p : kPseudo) {
|
for (const char* p : kPseudo) {
|
||||||
if (fsType == QByteArrayView(p)) {
|
if (fsType == QByteArrayView(p)) {
|
||||||
@@ -194,7 +180,7 @@ QStringList Storage::resolveToPhysicalDisks(const QString& devicePath) {
|
|||||||
if (devicePath.isEmpty() || !devicePath.startsWith(QLatin1Char('/'))) {
|
if (devicePath.isEmpty() || !devicePath.startsWith(QLatin1Char('/'))) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
struct stat st {};
|
struct stat st{};
|
||||||
if (::stat(devicePath.toLocal8Bit().constData(), &st) != 0) {
|
if (::stat(devicePath.toLocal8Bit().constData(), &st) != 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -232,7 +218,8 @@ void Storage::tick() {
|
|||||||
const QByteArray device = v.device();
|
const QByteArray device = v.device();
|
||||||
const auto totalBytes = static_cast<quint64>(v.bytesTotal());
|
const auto totalBytes = static_cast<quint64>(v.bytesTotal());
|
||||||
const auto availBytes = static_cast<quint64>(v.bytesAvailable());
|
const auto availBytes = static_cast<quint64>(v.bytesAvailable());
|
||||||
const quint64 usedBytes = totalBytes > availBytes ? totalBytes - availBytes : 0;
|
const quint64 usedBytes =
|
||||||
|
totalBytes > availBytes ? totalBytes - availBytes : 0;
|
||||||
const bool isRoot = v.rootPath() == QStringLiteral("/");
|
const bool isRoot = v.rootPath() == QStringLiteral("/");
|
||||||
|
|
||||||
DeviceEntry& e = byDevice[device];
|
DeviceEntry& e = byDevice[device];
|
||||||
@@ -244,7 +231,8 @@ void Storage::tick() {
|
|||||||
|
|
||||||
for (auto it = byDevice.constBegin(); it != byDevice.constEnd(); ++it) {
|
for (auto it = byDevice.constBegin(); it != byDevice.constEnd(); ++it) {
|
||||||
const DeviceEntry& e = it.value();
|
const DeviceEntry& e = it.value();
|
||||||
const QStringList disks = resolveToPhysicalDisks(QString::fromLocal8Bit(e.device));
|
const QStringList disks =
|
||||||
|
resolveToPhysicalDisks(QString::fromLocal8Bit(e.device));
|
||||||
if (disks.isEmpty()) {
|
if (disks.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -269,14 +257,23 @@ void Storage::tick() {
|
|||||||
next.reserve(byDisk.size());
|
next.reserve(byDisk.size());
|
||||||
for (auto it = byDisk.constBegin(); it != byDisk.constEnd(); ++it) {
|
for (auto it = byDisk.constBegin(); it != byDisk.constEnd(); ++it) {
|
||||||
if (DiskInfo* survivor = existing.take(it.key())) {
|
if (DiskInfo* survivor = existing.take(it.key())) {
|
||||||
survivor->update(it.value().usedBytes, it.value().totalBytes, it.value().hasRoot);
|
survivor->update(
|
||||||
|
it.value().usedBytes,
|
||||||
|
it.value().totalBytes,
|
||||||
|
it.value().hasRoot);
|
||||||
next.append(survivor);
|
next.append(survivor);
|
||||||
} else {
|
} else {
|
||||||
next.append(new DiskInfo(it.key(), it.value().usedBytes, it.value().totalBytes, it.value().hasRoot, this));
|
next.append(new DiskInfo(
|
||||||
|
it.key(),
|
||||||
|
it.value().usedBytes,
|
||||||
|
it.value().totalBytes,
|
||||||
|
it.value().hasRoot,
|
||||||
|
this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) {
|
std::sort(
|
||||||
|
next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) {
|
||||||
if (a->hasRoot() != b->hasRoot()) {
|
if (a->hasRoot() != b->hasRoot()) {
|
||||||
return a->hasRoot();
|
return a->hasRoot();
|
||||||
}
|
}
|
||||||
@@ -284,7 +281,8 @@ void Storage::tick() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
bool manualCleared = false;
|
bool manualCleared = false;
|
||||||
if (DiskInfo* m = m_manualPrimaryDisk.data(); m && existing.contains(m->mount())) {
|
if (DiskInfo* m = m_manualPrimaryDisk.data();
|
||||||
|
m && existing.contains(m->mount())) {
|
||||||
m_manualPrimaryDisk.clear();
|
m_manualPrimaryDisk.clear();
|
||||||
manualCleared = true;
|
manualCleared = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,44 +12,51 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class Storage : public TickingService {
|
class Storage : public TickingService {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged)
|
||||||
Q_PROPERTY(QQmlListProperty<ZShell::services::DiskInfo> disks READ disksProp NOTIFY disksChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk WRITE setManualPrimaryDisk NOTIFY
|
QQmlListProperty<ZShell::services::DiskInfo> disks READ disksProp NOTIFY
|
||||||
manualPrimaryDiskChanged)
|
disksChanged)
|
||||||
Q_PROPERTY(ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY primaryDiskChanged)
|
Q_PROPERTY(
|
||||||
|
ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk
|
||||||
|
WRITE setManualPrimaryDisk NOTIFY manualPrimaryDiskChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY
|
||||||
|
primaryDiskChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Storage(QObject* parent = nullptr);
|
explicit Storage(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] qreal percentage() const;
|
[[nodiscard]] qreal percentage() const;
|
||||||
[[nodiscard]] QQmlListProperty<DiskInfo> disksProp();
|
[[nodiscard]] QQmlListProperty<DiskInfo> disksProp();
|
||||||
[[nodiscard]] DiskInfo* manualPrimaryDisk() const;
|
[[nodiscard]] DiskInfo* manualPrimaryDisk() const;
|
||||||
void setManualPrimaryDisk(DiskInfo* disk);
|
void setManualPrimaryDisk(DiskInfo* disk);
|
||||||
[[nodiscard]] DiskInfo* primaryDisk() const;
|
[[nodiscard]] DiskInfo* primaryDisk() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void disksChanged();
|
void disksChanged();
|
||||||
void percentageChanged();
|
void percentageChanged();
|
||||||
void manualPrimaryDiskChanged();
|
void manualPrimaryDiskChanged();
|
||||||
void primaryDiskChanged();
|
void primaryDiskChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void tick() override;
|
void tick() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] static QStringList resolveToPhysicalDisks(const QString& devicePath);
|
[[nodiscard]] static QStringList resolveToPhysicalDisks(
|
||||||
[[nodiscard]] static bool isPseudoFs(QByteArrayView fsType);
|
const QString& devicePath);
|
||||||
[[nodiscard]] static bool sameOrder(const QList<DiskInfo*>& a, const QList<DiskInfo*>& b);
|
[[nodiscard]] static bool isPseudoFs(QByteArrayView fsType);
|
||||||
|
[[nodiscard]] static bool sameOrder(
|
||||||
|
const QList<DiskInfo*>& a, const QList<DiskInfo*>& b);
|
||||||
|
|
||||||
static qsizetype disksCount(QQmlListProperty<DiskInfo>* prop);
|
static qsizetype disksCount(QQmlListProperty<DiskInfo>* prop);
|
||||||
static DiskInfo* disksAt(QQmlListProperty<DiskInfo>* prop, qsizetype i);
|
static DiskInfo* disksAt(QQmlListProperty<DiskInfo>* prop, qsizetype i);
|
||||||
|
|
||||||
QList<DiskInfo*> m_disks;
|
QList<DiskInfo*> m_disks;
|
||||||
QPointer<DiskInfo> m_manualPrimaryDisk;
|
QPointer<DiskInfo> m_manualPrimaryDisk;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -6,30 +6,31 @@
|
|||||||
namespace ZShell::services {
|
namespace ZShell::services {
|
||||||
|
|
||||||
class TickingService : public Service {
|
class TickingService : public Service {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(int updateInterval READ updateInterval NOTIFY updateIntervalChanged)
|
Q_PROPERTY(
|
||||||
|
int updateInterval READ updateInterval NOTIFY updateIntervalChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TickingService(QObject* parent = nullptr);
|
explicit TickingService(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] int updateInterval() const;
|
[[nodiscard]] int updateInterval() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateIntervalChanged();
|
void updateIntervalChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void start() final;
|
void start() final;
|
||||||
void stop() final;
|
void stop() final;
|
||||||
|
|
||||||
virtual void tick() = 0;
|
virtual void tick() = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyInterval(int ms);
|
void applyInterval(int ms);
|
||||||
|
|
||||||
QTimer* m_timer;
|
QTimer* m_timer;
|
||||||
int m_interval = 1000;
|
int m_interval = 1000;
|
||||||
bool m_running = false;
|
bool m_running = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services
|
} // namespace ZShell::services
|
||||||
|
|||||||
@@ -16,18 +16,18 @@ namespace ZShell::services::usagefmt {
|
|||||||
|
|
||||||
FormatResult UsageFmt::formatKib(qreal kib, qreal total) const {
|
FormatResult UsageFmt::formatKib(qreal kib, qreal total) const {
|
||||||
if (!finitePositive(kib) || !finitePositive(total)) {
|
if (!finitePositive(kib) || !finitePositive(total)) {
|
||||||
return { 0.0, 0.0, "KiB" };
|
return {0.0, 0.0, "KiB"};
|
||||||
}
|
}
|
||||||
if (total >= kGib) {
|
if (total >= kGib) {
|
||||||
return { kib / kGib, total / kGib, "TiB" };
|
return {kib / kGib, total / kGib, "TiB"};
|
||||||
}
|
}
|
||||||
if (total >= kMib) {
|
if (total >= kMib) {
|
||||||
return { kib / kMib, total / kMib, "GiB" };
|
return {kib / kMib, total / kMib, "GiB"};
|
||||||
}
|
}
|
||||||
if (total >= kKib) {
|
if (total >= kKib) {
|
||||||
return { kib / kKib, total / kKib, "MiB" };
|
return {kib / kKib, total / kKib, "MiB"};
|
||||||
}
|
}
|
||||||
return { kib, total, "KiB" };
|
return {kib, total, "KiB"};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::services::usagefmt
|
} // namespace ZShell::services::usagefmt
|
||||||
|
|||||||
@@ -15,19 +15,20 @@ struct FormatResult {
|
|||||||
Q_PROPERTY(qreal total MEMBER total CONSTANT)
|
Q_PROPERTY(qreal total MEMBER total CONSTANT)
|
||||||
Q_PROPERTY(QString unit MEMBER unit CONSTANT)
|
Q_PROPERTY(QString unit MEMBER unit CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
qreal value;
|
qreal value;
|
||||||
qreal total;
|
qreal total;
|
||||||
QString unit;
|
QString unit;
|
||||||
};
|
};
|
||||||
|
|
||||||
class UsageFmt : public QObject {
|
class UsageFmt : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE [[nodiscard]] FormatResult formatKib(qreal kib, qreal total) const;
|
Q_INVOKABLE [[nodiscard]] FormatResult formatKib(
|
||||||
|
qreal kib, qreal total) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::services::usagefmt
|
} // namespace ZShell::services::usagefmt
|
||||||
|
|||||||
+169
-157
@@ -7,259 +7,271 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
|
AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent), m_entry(entry), m_frequency(frequency) {
|
||||||
, m_entry(entry)
|
const auto mo = m_entry->metaObject();
|
||||||
, m_frequency(frequency) {
|
const auto tmo = metaObject();
|
||||||
const auto mo = m_entry->metaObject();
|
|
||||||
const auto tmo = metaObject();
|
|
||||||
|
|
||||||
for (const auto& prop :
|
for (const auto& prop :
|
||||||
{ "name", "comment", "execString", "startupClass", "genericName", "categories", "keywords" }) {
|
{"name",
|
||||||
const auto metaProp = mo->property(mo->indexOfProperty(prop));
|
"comment",
|
||||||
const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop));
|
"execString",
|
||||||
QObject::connect(m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal());
|
"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, &QObject::destroyed, this, [this]() {
|
QObject::connect(m_entry, &QObject::destroyed, this, [this]() {
|
||||||
m_entry = nullptr;
|
m_entry = nullptr;
|
||||||
deleteLater();
|
deleteLater();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QObject* AppEntry::entry() const {
|
QObject* AppEntry::entry() const {
|
||||||
return m_entry;
|
return m_entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
quint32 AppEntry::frequency() const {
|
quint32 AppEntry::frequency() const {
|
||||||
return m_frequency;
|
return m_frequency;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppEntry::setFrequency(unsigned int frequency) {
|
void AppEntry::setFrequency(unsigned int frequency) {
|
||||||
if (m_frequency != frequency) {
|
if (m_frequency != frequency) {
|
||||||
m_frequency = frequency;
|
m_frequency = frequency;
|
||||||
emit frequencyChanged();
|
emit frequencyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppEntry::incrementFrequency() {
|
void AppEntry::incrementFrequency() {
|
||||||
m_frequency++;
|
m_frequency++;
|
||||||
emit frequencyChanged();
|
emit frequencyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::id() const {
|
QString AppEntry::id() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("id").toString();
|
return m_entry->property("id").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::name() const {
|
QString AppEntry::name() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("name").toString();
|
return m_entry->property("name").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::comment() const {
|
QString AppEntry::comment() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("comment").toString();
|
return m_entry->property("comment").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::execString() const {
|
QString AppEntry::execString() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("execString").toString();
|
return m_entry->property("execString").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::startupClass() const {
|
QString AppEntry::startupClass() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("startupClass").toString();
|
return m_entry->property("startupClass").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::genericName() const {
|
QString AppEntry::genericName() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("genericName").toString();
|
return m_entry->property("genericName").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::categories() const {
|
QString AppEntry::categories() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("categories").toStringList().join(" ");
|
return m_entry->property("categories").toStringList().join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppEntry::keywords() const {
|
QString AppEntry::keywords() const {
|
||||||
if (!m_entry) {
|
if (!m_entry) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return m_entry->property("keywords").toStringList().join(" ");
|
return m_entry->property("keywords").toStringList().join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppDb::AppDb(QObject* parent)
|
AppDb::AppDb(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_timer(new QTimer(this))
|
, m_timer(new QTimer(this))
|
||||||
, m_uuid(QUuid::createUuid().toString()) {
|
, m_uuid(QUuid::createUuid().toString()) {
|
||||||
m_timer->setSingleShot(true);
|
m_timer->setSingleShot(true);
|
||||||
m_timer->setInterval(300);
|
m_timer->setInterval(300);
|
||||||
QObject::connect(m_timer, &QTimer::timeout, this, &AppDb::updateApps);
|
QObject::connect(m_timer, &QTimer::timeout, this, &AppDb::updateApps);
|
||||||
|
|
||||||
auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid);
|
auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid);
|
||||||
db.setDatabaseName(":memory:");
|
db.setDatabaseName(":memory:");
|
||||||
db.open();
|
db.open();
|
||||||
|
|
||||||
QSqlQuery query(db);
|
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 {
|
QString AppDb::uuid() const {
|
||||||
return m_uuid;
|
return m_uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AppDb::path() const {
|
QString AppDb::path() const {
|
||||||
return m_path;
|
return m_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppDb::setPath(const QString& path) {
|
void AppDb::setPath(const QString& path) {
|
||||||
auto newPath = path.isEmpty() ? ":memory:" : path;
|
auto newPath = path.isEmpty() ? ":memory:" : path;
|
||||||
|
|
||||||
if (m_path == newPath) {
|
if (m_path == newPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_path = newPath;
|
m_path = newPath;
|
||||||
emit pathChanged();
|
emit pathChanged();
|
||||||
|
|
||||||
auto db = QSqlDatabase::database(m_uuid, false);
|
auto db = QSqlDatabase::database(m_uuid, false);
|
||||||
db.close();
|
db.close();
|
||||||
db.setDatabaseName(newPath);
|
db.setDatabaseName(newPath);
|
||||||
db.open();
|
db.open();
|
||||||
|
|
||||||
QSqlQuery query(db);
|
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();
|
updateAppFrequencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
QObjectList AppDb::entries() const {
|
QObjectList AppDb::entries() const {
|
||||||
return m_entries;
|
return m_entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppDb::setEntries(const QObjectList& entries) {
|
void AppDb::setEntries(const QObjectList& entries) {
|
||||||
if (m_entries == entries) {
|
if (m_entries == entries) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_entries = entries;
|
m_entries = entries;
|
||||||
emit entriesChanged();
|
emit entriesChanged();
|
||||||
|
|
||||||
m_timer->start();
|
m_timer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQmlListProperty<AppEntry> AppDb::apps() {
|
QQmlListProperty<AppEntry> AppDb::apps() {
|
||||||
return QQmlListProperty<AppEntry>(this, &getSortedApps());
|
return QQmlListProperty<AppEntry>(this, &getSortedApps());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppDb::incrementFrequency(const QString& id) {
|
void AppDb::incrementFrequency(const QString& id) {
|
||||||
auto db = QSqlDatabase::database(m_uuid);
|
auto db = QSqlDatabase::database(m_uuid);
|
||||||
QSqlQuery query(db);
|
QSqlQuery query(db);
|
||||||
|
|
||||||
query.prepare("INSERT INTO frequencies (id, frequency) "
|
query.prepare(
|
||||||
"VALUES (:id, 1) "
|
"INSERT INTO frequencies (id, frequency) "
|
||||||
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
|
"VALUES (:id, 1) "
|
||||||
query.bindValue(":id", id);
|
"ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
|
||||||
query.exec();
|
query.bindValue(":id", id);
|
||||||
|
query.exec();
|
||||||
|
|
||||||
auto* app = m_apps.value(id);
|
auto* app = m_apps.value(id);
|
||||||
if (app) {
|
if (app) {
|
||||||
const auto before = getSortedApps();
|
const auto before = getSortedApps();
|
||||||
|
|
||||||
app->incrementFrequency();
|
app->incrementFrequency();
|
||||||
|
|
||||||
if (before != getSortedApps()) {
|
if (before != getSortedApps()) {
|
||||||
emit appsChanged();
|
emit appsChanged();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "AppDb::incrementFrequency: could not find app with id" << id;
|
qWarning() << "AppDb::incrementFrequency: could not find app with id"
|
||||||
}
|
<< id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<AppEntry*>& AppDb::getSortedApps() const {
|
QList<AppEntry*>& AppDb::getSortedApps() const {
|
||||||
m_sortedApps = m_apps.values();
|
m_sortedApps = m_apps.values();
|
||||||
std::sort(m_sortedApps.begin(), m_sortedApps.end(), [](AppEntry* a, AppEntry* b) {
|
std::sort(
|
||||||
if (a->frequency() != b->frequency()) {
|
m_sortedApps.begin(), m_sortedApps.end(), [](AppEntry* a, AppEntry* b) {
|
||||||
return a->frequency() > b->frequency();
|
if (a->frequency() != b->frequency()) {
|
||||||
}
|
return a->frequency() > b->frequency();
|
||||||
return a->name().localeAwareCompare(b->name()) < 0;
|
}
|
||||||
});
|
return a->name().localeAwareCompare(b->name()) < 0;
|
||||||
return m_sortedApps;
|
});
|
||||||
|
return m_sortedApps;
|
||||||
}
|
}
|
||||||
|
|
||||||
quint32 AppDb::getFrequency(const QString& id) const {
|
quint32 AppDb::getFrequency(const QString& id) const {
|
||||||
auto db = QSqlDatabase::database(m_uuid);
|
auto db = QSqlDatabase::database(m_uuid);
|
||||||
QSqlQuery query(db);
|
QSqlQuery query(db);
|
||||||
|
|
||||||
query.prepare("SELECT frequency FROM frequencies WHERE id = :id");
|
query.prepare("SELECT frequency FROM frequencies WHERE id = :id");
|
||||||
query.bindValue(":id", id);
|
query.bindValue(":id", id);
|
||||||
|
|
||||||
if (query.exec() && query.next()) {
|
if (query.exec() && query.next()) {
|
||||||
return query.value(0).toUInt();
|
return query.value(0).toUInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppDb::updateAppFrequencies() {
|
void AppDb::updateAppFrequencies() {
|
||||||
const auto before = getSortedApps();
|
const auto before = getSortedApps();
|
||||||
|
|
||||||
for (auto* app : std::as_const(m_apps)) {
|
for (auto* app : std::as_const(m_apps)) {
|
||||||
app->setFrequency(getFrequency(app->id()));
|
app->setFrequency(getFrequency(app->id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (before != getSortedApps()) {
|
if (before != getSortedApps()) {
|
||||||
emit appsChanged();
|
emit appsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppDb::updateApps() {
|
void AppDb::updateApps() {
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
|
|
||||||
for (const auto& entry : std::as_const(m_entries)) {
|
for (const auto& entry : std::as_const(m_entries)) {
|
||||||
const auto id = entry->property("id").toString();
|
const auto id = entry->property("id").toString();
|
||||||
if (!m_apps.contains(id)) {
|
if (!m_apps.contains(id)) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
auto* const newEntry = new AppEntry(entry, getFrequency(id), this);
|
auto* const newEntry = new AppEntry(entry, getFrequency(id), this);
|
||||||
QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() {
|
QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() {
|
||||||
if (m_apps.remove(id)) {
|
if (m_apps.remove(id)) {
|
||||||
emit appsChanged();
|
emit appsChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m_apps.insert(id, newEntry);
|
m_apps.insert(id, newEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<QString> newIds;
|
QSet<QString> newIds;
|
||||||
for (const auto& entry : std::as_const(m_entries)) {
|
for (const auto& entry : std::as_const(m_entries)) {
|
||||||
newIds.insert(entry->property("id").toString());
|
newIds.insert(entry->property("id").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = m_apps.keyBegin(); it != m_apps.keyEnd(); ++it) {
|
for (auto it = m_apps.keyBegin(); it != m_apps.keyEnd(); ++it) {
|
||||||
const auto& id = *it;
|
const auto& id = *it;
|
||||||
if (!newIds.contains(id)) {
|
if (!newIds.contains(id)) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
m_apps.take(id)->deleteLater();
|
m_apps.take(id)->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
emit appsChanged();
|
emit appsChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
+74
-70
@@ -9,98 +9,102 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class AppEntry : public QObject {
|
class AppEntry : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb")
|
QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb")
|
||||||
|
|
||||||
// The actual DesktopEntry, but we don't have access to the type so it's a QObject
|
// The actual DesktopEntry, but we don't have access to the type so it's a QObject
|
||||||
Q_PROPERTY(QObject* entry READ entry CONSTANT)
|
Q_PROPERTY(QObject* entry READ entry CONSTANT)
|
||||||
|
|
||||||
Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged)
|
Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged)
|
||||||
Q_PROPERTY(QString id READ id CONSTANT)
|
Q_PROPERTY(QString id READ id CONSTANT)
|
||||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||||
Q_PROPERTY(QString comment READ comment NOTIFY commentChanged)
|
Q_PROPERTY(QString comment READ comment NOTIFY commentChanged)
|
||||||
Q_PROPERTY(QString execString READ execString NOTIFY execStringChanged)
|
Q_PROPERTY(QString execString READ execString NOTIFY execStringChanged)
|
||||||
Q_PROPERTY(QString startupClass READ startupClass NOTIFY startupClassChanged)
|
Q_PROPERTY(QString startupClass READ startupClass NOTIFY startupClassChanged)
|
||||||
Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged)
|
Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged)
|
||||||
Q_PROPERTY(QString categories READ categories NOTIFY categoriesChanged)
|
Q_PROPERTY(QString categories READ categories NOTIFY categoriesChanged)
|
||||||
Q_PROPERTY(QString keywords READ keywords NOTIFY keywordsChanged)
|
Q_PROPERTY(QString keywords READ keywords NOTIFY keywordsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AppEntry(QObject* entry, quint32 frequency, QObject* parent = nullptr);
|
explicit AppEntry(
|
||||||
|
QObject* entry, quint32 frequency, QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QObject* entry() const;
|
[[nodiscard]] QObject* entry() const;
|
||||||
|
|
||||||
[[nodiscard]] quint32 frequency() const;
|
[[nodiscard]] quint32 frequency() const;
|
||||||
void setFrequency(quint32 frequency);
|
void setFrequency(quint32 frequency);
|
||||||
void incrementFrequency();
|
void incrementFrequency();
|
||||||
|
|
||||||
[[nodiscard]] QString id() const;
|
[[nodiscard]] QString id() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString comment() const;
|
[[nodiscard]] QString comment() const;
|
||||||
[[nodiscard]] QString execString() const;
|
[[nodiscard]] QString execString() const;
|
||||||
[[nodiscard]] QString startupClass() const;
|
[[nodiscard]] QString startupClass() const;
|
||||||
[[nodiscard]] QString genericName() const;
|
[[nodiscard]] QString genericName() const;
|
||||||
[[nodiscard]] QString categories() const;
|
[[nodiscard]] QString categories() const;
|
||||||
[[nodiscard]] QString keywords() const;
|
[[nodiscard]] QString keywords() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void frequencyChanged();
|
void frequencyChanged();
|
||||||
void nameChanged();
|
void nameChanged();
|
||||||
void commentChanged();
|
void commentChanged();
|
||||||
void execStringChanged();
|
void execStringChanged();
|
||||||
void startupClassChanged();
|
void startupClassChanged();
|
||||||
void genericNameChanged();
|
void genericNameChanged();
|
||||||
void categoriesChanged();
|
void categoriesChanged();
|
||||||
void keywordsChanged();
|
void keywordsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QObject* m_entry;
|
QObject* m_entry;
|
||||||
quint32 m_frequency;
|
quint32 m_frequency;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AppDb : public QObject {
|
class AppDb : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QString uuid READ uuid CONSTANT)
|
Q_PROPERTY(QString uuid READ uuid CONSTANT)
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
|
||||||
Q_PROPERTY(QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged
|
||||||
|
REQUIRED)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QQmlListProperty<ZShell::AppEntry> apps READ apps NOTIFY appsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AppDb(QObject* parent = nullptr);
|
explicit AppDb(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QString uuid() const;
|
[[nodiscard]] QString uuid() const;
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
void setPath(const QString& path);
|
void setPath(const QString& path);
|
||||||
|
|
||||||
[[nodiscard]] QObjectList entries() const;
|
[[nodiscard]] QObjectList entries() const;
|
||||||
void setEntries(const QObjectList& entries);
|
void setEntries(const QObjectList& entries);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<AppEntry> apps();
|
[[nodiscard]] QQmlListProperty<AppEntry> apps();
|
||||||
|
|
||||||
Q_INVOKABLE void incrementFrequency(const QString& id);
|
Q_INVOKABLE void incrementFrequency(const QString& id);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void entriesChanged();
|
void entriesChanged();
|
||||||
void appsChanged();
|
void appsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QTimer* m_timer;
|
QTimer* m_timer;
|
||||||
|
|
||||||
const QString m_uuid;
|
const QString m_uuid;
|
||||||
QString m_path;
|
QString m_path;
|
||||||
QObjectList m_entries;
|
QObjectList m_entries;
|
||||||
QHash<QString, AppEntry*> m_apps;
|
QHash<QString, AppEntry*> m_apps;
|
||||||
mutable QList<AppEntry*> m_sortedApps;
|
mutable QList<AppEntry*> m_sortedApps;
|
||||||
|
|
||||||
QList<AppEntry*>& getSortedApps() const;
|
QList<AppEntry*>& getSortedApps() const;
|
||||||
quint32 getFrequency(const QString& id) const;
|
quint32 getFrequency(const QString& id) const;
|
||||||
void updateAppFrequencies();
|
void updateAppFrequencies();
|
||||||
void updateApps();
|
void updateApps();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
+190
-150
@@ -9,215 +9,255 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
ImageAnalyser::ImageAnalyser(QObject* parent)
|
ImageAnalyser::ImageAnalyser(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_futureWatcher(new QFutureWatcher<AnalyseResult>(this))
|
, m_futureWatcher(new QFutureWatcher<AnalyseResult>(this))
|
||||||
, m_source("")
|
, m_source("")
|
||||||
, m_sourceItem(nullptr)
|
, m_sourceItem(nullptr)
|
||||||
, m_rescaleSize(128)
|
, m_rescaleSize(128)
|
||||||
, m_dominantColour(0, 0, 0)
|
, m_dominantColour(0, 0, 0)
|
||||||
, m_luminance(0) {
|
, m_luminance(0) {
|
||||||
QObject::connect(m_futureWatcher, &QFutureWatcher<AnalyseResult>::finished, this, [this]() {
|
QObject::connect(
|
||||||
if (!m_futureWatcher->future().isResultReadyAt(0)) {
|
m_futureWatcher,
|
||||||
return;
|
&QFutureWatcher<AnalyseResult>::finished,
|
||||||
}
|
this,
|
||||||
|
[this]() {
|
||||||
|
if (!m_futureWatcher->future().isResultReadyAt(0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto result = m_futureWatcher->result();
|
const auto result = m_futureWatcher->result();
|
||||||
if (m_dominantColour != result.first) {
|
if (m_dominantColour != result.first) {
|
||||||
m_dominantColour = result.first;
|
m_dominantColour = result.first;
|
||||||
emit dominantColourChanged();
|
emit dominantColourChanged();
|
||||||
}
|
}
|
||||||
if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) {
|
if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) {
|
||||||
m_luminance = result.second;
|
m_luminance = result.second;
|
||||||
emit luminanceChanged();
|
emit luminanceChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ImageAnalyser::source() const {
|
QString ImageAnalyser::source() const {
|
||||||
return m_source;
|
return m_source;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageAnalyser::setSource(const QString& source) {
|
void ImageAnalyser::setSource(const QString& source) {
|
||||||
if (m_source == source) {
|
if (m_source == source) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_source = source;
|
m_source = source;
|
||||||
emit sourceChanged();
|
emit sourceChanged();
|
||||||
|
|
||||||
if (m_sourceItem) {
|
if (m_sourceItem) {
|
||||||
m_sourceItem = nullptr;
|
m_sourceItem = nullptr;
|
||||||
emit sourceItemChanged();
|
emit sourceItemChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
QQuickItem* ImageAnalyser::sourceItem() const {
|
QQuickItem* ImageAnalyser::sourceItem() const {
|
||||||
return m_sourceItem;
|
return m_sourceItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageAnalyser::setSourceItem(QQuickItem* sourceItem) {
|
void ImageAnalyser::setSourceItem(QQuickItem* sourceItem) {
|
||||||
if (m_sourceItem == sourceItem) {
|
if (m_sourceItem == sourceItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_sourceItem = sourceItem;
|
m_sourceItem = sourceItem;
|
||||||
emit sourceItemChanged();
|
emit sourceItemChanged();
|
||||||
|
|
||||||
if (!m_source.isEmpty()) {
|
if (!m_source.isEmpty()) {
|
||||||
m_source = "";
|
m_source = "";
|
||||||
emit sourceChanged();
|
emit sourceChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
int ImageAnalyser::rescaleSize() const {
|
int ImageAnalyser::rescaleSize() const {
|
||||||
return m_rescaleSize;
|
return m_rescaleSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageAnalyser::setRescaleSize(int rescaleSize) {
|
void ImageAnalyser::setRescaleSize(int rescaleSize) {
|
||||||
if (m_rescaleSize == rescaleSize) {
|
if (m_rescaleSize == rescaleSize) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rescaleSize = rescaleSize;
|
m_rescaleSize = rescaleSize;
|
||||||
emit rescaleSizeChanged();
|
emit rescaleSizeChanged();
|
||||||
|
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor ImageAnalyser::dominantColour() const {
|
QColor ImageAnalyser::dominantColour() const {
|
||||||
return m_dominantColour;
|
return m_dominantColour;
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal ImageAnalyser::luminance() const {
|
qreal ImageAnalyser::luminance() const {
|
||||||
return m_luminance;
|
return m_luminance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageAnalyser::requestUpdate() {
|
void ImageAnalyser::requestUpdate() {
|
||||||
if (m_source.isEmpty() && !m_sourceItem) {
|
if (m_source.isEmpty() && !m_sourceItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_sourceItem || (m_sourceItem->window() && m_sourceItem->window()->isVisible() && m_sourceItem->width() > 0 &&
|
if (!m_sourceItem ||
|
||||||
m_sourceItem->height() > 0)) {
|
(m_sourceItem->window() && m_sourceItem->window()->isVisible() &&
|
||||||
update();
|
m_sourceItem->width() > 0 && m_sourceItem->height() > 0)) {
|
||||||
} else if (m_sourceItem) {
|
update();
|
||||||
if (!m_sourceItem->window()) {
|
} else if (m_sourceItem) {
|
||||||
QObject::connect(m_sourceItem, &QQuickItem::windowChanged, this, &ImageAnalyser::requestUpdate,
|
if (!m_sourceItem->window()) {
|
||||||
Qt::SingleShotConnection);
|
QObject::connect(
|
||||||
} else if (!m_sourceItem->window()->isVisible()) {
|
m_sourceItem,
|
||||||
QObject::connect(m_sourceItem->window(), &QQuickWindow::visibleChanged, this, &ImageAnalyser::requestUpdate,
|
&QQuickItem::windowChanged,
|
||||||
Qt::SingleShotConnection);
|
this,
|
||||||
}
|
&ImageAnalyser::requestUpdate,
|
||||||
if (m_sourceItem->width() <= 0) {
|
Qt::SingleShotConnection);
|
||||||
QObject::connect(
|
} else if (!m_sourceItem->window()->isVisible()) {
|
||||||
m_sourceItem, &QQuickItem::widthChanged, this, &ImageAnalyser::requestUpdate, Qt::SingleShotConnection);
|
QObject::connect(
|
||||||
}
|
m_sourceItem->window(),
|
||||||
if (m_sourceItem->height() <= 0) {
|
&QQuickWindow::visibleChanged,
|
||||||
QObject::connect(m_sourceItem, &QQuickItem::heightChanged, this, &ImageAnalyser::requestUpdate,
|
this,
|
||||||
Qt::SingleShotConnection);
|
&ImageAnalyser::requestUpdate,
|
||||||
}
|
Qt::SingleShotConnection);
|
||||||
}
|
}
|
||||||
|
if (m_sourceItem->width() <= 0) {
|
||||||
|
QObject::connect(
|
||||||
|
m_sourceItem,
|
||||||
|
&QQuickItem::widthChanged,
|
||||||
|
this,
|
||||||
|
&ImageAnalyser::requestUpdate,
|
||||||
|
Qt::SingleShotConnection);
|
||||||
|
}
|
||||||
|
if (m_sourceItem->height() <= 0) {
|
||||||
|
QObject::connect(
|
||||||
|
m_sourceItem,
|
||||||
|
&QQuickItem::heightChanged,
|
||||||
|
this,
|
||||||
|
&ImageAnalyser::requestUpdate,
|
||||||
|
Qt::SingleShotConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageAnalyser::update() {
|
void ImageAnalyser::update() {
|
||||||
if (m_source.isEmpty() && !m_sourceItem) {
|
if (m_source.isEmpty() && !m_sourceItem) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_futureWatcher->isRunning()) {
|
if (m_futureWatcher->isRunning()) {
|
||||||
m_futureWatcher->cancel();
|
m_futureWatcher->cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_sourceItem) {
|
if (m_sourceItem) {
|
||||||
const QSharedPointer<const QQuickItemGrabResult> grabResult = m_sourceItem->grabToImage();
|
const QSharedPointer<const QQuickItemGrabResult> grabResult =
|
||||||
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, this]() {
|
m_sourceItem->grabToImage();
|
||||||
m_futureWatcher->setFuture(QtConcurrent::run(&ImageAnalyser::analyse, grabResult->image(), m_rescaleSize));
|
QObject::connect(
|
||||||
});
|
grabResult.data(),
|
||||||
} else {
|
&QQuickItemGrabResult::ready,
|
||||||
m_futureWatcher->setFuture(QtConcurrent::run([=, this](QPromise<AnalyseResult>& promise) {
|
this,
|
||||||
const QImage image(m_source);
|
[grabResult, this]() {
|
||||||
analyse(promise, image, m_rescaleSize);
|
m_futureWatcher->setFuture(
|
||||||
}));
|
QtConcurrent::run(
|
||||||
}
|
&ImageAnalyser::analyse,
|
||||||
|
grabResult->image(),
|
||||||
|
m_rescaleSize));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
m_futureWatcher->setFuture(
|
||||||
|
QtConcurrent::run([=, this](QPromise<AnalyseResult>& promise) {
|
||||||
|
const QImage image(m_source);
|
||||||
|
analyse(promise, image, m_rescaleSize);
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageAnalyser::analyse(QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize) {
|
void ImageAnalyser::analyse(
|
||||||
if (image.isNull()) {
|
QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize) {
|
||||||
qWarning() << "ImageAnalyser::analyse: image is null";
|
if (image.isNull()) {
|
||||||
return;
|
qWarning() << "ImageAnalyser::analyse: image is null";
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QImage img = image;
|
QImage img = image;
|
||||||
|
|
||||||
if (rescaleSize > 0 && (img.width() > rescaleSize || img.height() > rescaleSize)) {
|
if (rescaleSize > 0 &&
|
||||||
img = img.scaled(rescaleSize, rescaleSize, Qt::KeepAspectRatio, Qt::FastTransformation);
|
(img.width() > rescaleSize || img.height() > rescaleSize)) {
|
||||||
}
|
img = img.scaled(
|
||||||
|
rescaleSize,
|
||||||
|
rescaleSize,
|
||||||
|
Qt::KeepAspectRatio,
|
||||||
|
Qt::FastTransformation);
|
||||||
|
}
|
||||||
|
|
||||||
if (promise.isCanceled()) {
|
if (promise.isCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (img.format() != QImage::Format_ARGB32) {
|
if (img.format() != QImage::Format_ARGB32) {
|
||||||
img = img.convertToFormat(QImage::Format_ARGB32);
|
img = img.convertToFormat(QImage::Format_ARGB32);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (promise.isCanceled()) {
|
if (promise.isCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uchar* data = img.bits();
|
const uchar* data = img.bits();
|
||||||
const int width = img.width();
|
const int width = img.width();
|
||||||
const int height = img.height();
|
const int height = img.height();
|
||||||
const qsizetype bytesPerLine = img.bytesPerLine();
|
const qsizetype bytesPerLine = img.bytesPerLine();
|
||||||
|
|
||||||
std::unordered_map<quint32, int> colours;
|
std::unordered_map<quint32, int> colours;
|
||||||
qreal totalLuminance = 0.0;
|
qreal totalLuminance = 0.0;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (int y = 0; y < height; ++y) {
|
for (int y = 0; y < height; ++y) {
|
||||||
const uchar* line = data + y * bytesPerLine;
|
const uchar* line = data + y * bytesPerLine;
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x) {
|
||||||
if (promise.isCanceled()) {
|
if (promise.isCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uchar* pixel = line + x * 4;
|
const uchar* pixel = line + x * 4;
|
||||||
|
|
||||||
if (pixel[3] == 0) {
|
if (pixel[3] == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const quint32 mr = static_cast<quint32>(pixel[0] & 0xF8);
|
const quint32 mr = static_cast<quint32>(pixel[0] & 0xF8);
|
||||||
const quint32 mg = static_cast<quint32>(pixel[1] & 0xF8);
|
const quint32 mg = static_cast<quint32>(pixel[1] & 0xF8);
|
||||||
const quint32 mb = static_cast<quint32>(pixel[2] & 0xF8);
|
const quint32 mb = static_cast<quint32>(pixel[2] & 0xF8);
|
||||||
++colours[(mr << 16) | (mg << 8) | mb];
|
++colours[(mr << 16) | (mg << 8) | mb];
|
||||||
|
|
||||||
const qreal r = pixel[0] / 255.0;
|
const qreal r = pixel[0] / 255.0;
|
||||||
const qreal g = pixel[1] / 255.0;
|
const qreal g = pixel[1] / 255.0;
|
||||||
const qreal b = pixel[2] / 255.0;
|
const qreal b = pixel[2] / 255.0;
|
||||||
totalLuminance += std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
|
totalLuminance +=
|
||||||
++count;
|
std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b);
|
||||||
}
|
++count;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
quint32 dominantColour = 0;
|
quint32 dominantColour = 0;
|
||||||
int maxCount = 0;
|
int maxCount = 0;
|
||||||
for (const auto& [colour, colourCount] : colours) {
|
for (const auto& [colour, colourCount] : colours) {
|
||||||
if (promise.isCanceled()) {
|
if (promise.isCanceled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (colourCount > maxCount) {
|
if (colourCount > maxCount) {
|
||||||
dominantColour = colour;
|
dominantColour = colour;
|
||||||
maxCount = colourCount;
|
maxCount = colourCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
promise.addResult(qMakePair(QColor((0xFFu << 24) | dominantColour), count == 0 ? 0.0 : totalLuminance / count));
|
promise.addResult(qMakePair(
|
||||||
|
QColor((0xFFu << 24) | dominantColour),
|
||||||
|
count == 0 ? 0.0 : totalLuminance / count));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -9,53 +9,59 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class ImageAnalyser : public QObject {
|
class ImageAnalyser : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
|
Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged)
|
||||||
Q_PROPERTY(QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged)
|
Q_PROPERTY(
|
||||||
Q_PROPERTY(int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged)
|
QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY
|
||||||
Q_PROPERTY(QColor dominantColour READ dominantColour NOTIFY dominantColourChanged)
|
sourceItemChanged)
|
||||||
Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged)
|
Q_PROPERTY(
|
||||||
|
int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY
|
||||||
|
rescaleSizeChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
QColor dominantColour READ dominantColour NOTIFY dominantColourChanged)
|
||||||
|
Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ImageAnalyser(QObject* parent = nullptr);
|
explicit ImageAnalyser(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QString source() const;
|
[[nodiscard]] QString source() const;
|
||||||
void setSource(const QString& source);
|
void setSource(const QString& source);
|
||||||
|
|
||||||
[[nodiscard]] QQuickItem* sourceItem() const;
|
[[nodiscard]] QQuickItem* sourceItem() const;
|
||||||
void setSourceItem(QQuickItem* sourceItem);
|
void setSourceItem(QQuickItem* sourceItem);
|
||||||
|
|
||||||
[[nodiscard]] int rescaleSize() const;
|
[[nodiscard]] int rescaleSize() const;
|
||||||
void setRescaleSize(int rescaleSize);
|
void setRescaleSize(int rescaleSize);
|
||||||
|
|
||||||
[[nodiscard]] QColor dominantColour() const;
|
[[nodiscard]] QColor dominantColour() const;
|
||||||
[[nodiscard]] qreal luminance() const;
|
[[nodiscard]] qreal luminance() const;
|
||||||
|
|
||||||
Q_INVOKABLE void requestUpdate();
|
Q_INVOKABLE void requestUpdate();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sourceChanged();
|
void sourceChanged();
|
||||||
void sourceItemChanged();
|
void sourceItemChanged();
|
||||||
void rescaleSizeChanged();
|
void rescaleSizeChanged();
|
||||||
void dominantColourChanged();
|
void dominantColourChanged();
|
||||||
void luminanceChanged();
|
void luminanceChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using AnalyseResult = QPair<QColor, qreal>;
|
using AnalyseResult = QPair<QColor, qreal>;
|
||||||
|
|
||||||
QFutureWatcher<AnalyseResult>* const m_futureWatcher;
|
QFutureWatcher<AnalyseResult>* const m_futureWatcher;
|
||||||
|
|
||||||
QString m_source;
|
QString m_source;
|
||||||
QQuickItem* m_sourceItem;
|
QQuickItem* m_sourceItem;
|
||||||
int m_rescaleSize;
|
int m_rescaleSize;
|
||||||
|
|
||||||
QColor m_dominantColour;
|
QColor m_dominantColour;
|
||||||
qreal m_luminance;
|
qreal m_luminance;
|
||||||
|
|
||||||
void update();
|
void update();
|
||||||
static void analyse(QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize);
|
static void analyse(
|
||||||
|
QPromise<AnalyseResult>& promise, const QImage& image, int rescaleSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
|
|
||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
Qalculator::Qalculator(QObject* parent)
|
Qalculator::Qalculator(QObject* parent) : QObject(parent) {
|
||||||
: QObject(parent) {
|
|
||||||
if (!CALCULATOR) {
|
if (!CALCULATOR) {
|
||||||
new Calculator();
|
new Calculator();
|
||||||
CALCULATOR->loadExchangeRates();
|
CALCULATOR->loadExchangeRates();
|
||||||
@@ -24,7 +23,11 @@ QString Qalculator::eval(const QString& expr, bool printExpr) const {
|
|||||||
|
|
||||||
std::string parsed;
|
std::string parsed;
|
||||||
std::string result = CALCULATOR->calculateAndPrint(
|
std::string result = CALCULATOR->calculateAndPrint(
|
||||||
CALCULATOR->unlocalizeExpression(expr.toStdString(), eo.parse_options), 100, eo, po, &parsed);
|
CALCULATOR->unlocalizeExpression(expr.toStdString(), eo.parse_options),
|
||||||
|
100,
|
||||||
|
eo,
|
||||||
|
po,
|
||||||
|
&parsed);
|
||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
while (CALCULATOR->message()) {
|
while (CALCULATOR->message()) {
|
||||||
|
|||||||
@@ -6,14 +6,15 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class Qalculator : public QObject {
|
class Qalculator : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Qalculator(QObject* parent = nullptr);
|
explicit Qalculator(QObject* parent = nullptr);
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] QString eval(const QString& expr, bool printExpr = true) const;
|
Q_INVOKABLE [[nodiscard]] QString eval(
|
||||||
|
const QString& expr, bool printExpr = true) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
Requests::Requests(QObject* parent)
|
Requests::Requests(QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent), m_manager(new QNetworkAccessManager(this)) {}
|
||||||
, m_manager(new QNetworkAccessManager(this)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const {
|
void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const {
|
||||||
if (!onSuccess.isCallable()) {
|
if (!onSuccess.isCallable()) {
|
||||||
@@ -20,13 +18,15 @@ void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const
|
|||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
auto reply = m_manager->get(request);
|
auto reply = m_manager->get(request);
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, [reply, onSuccess, onError]() {
|
QObject::connect(
|
||||||
|
reply, &QNetworkReply::finished, [reply, onSuccess, onError]() {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
onSuccess.call({ QString(reply->readAll()) });
|
onSuccess.call({QString(reply->readAll())});
|
||||||
} else if (onError.isCallable()) {
|
} else if (onError.isCallable()) {
|
||||||
onError.call({ reply->errorString() });
|
onError.call({reply->errorString()});
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "Requests::get: request failed with error" << reply->errorString();
|
qWarning() << "Requests::get: request failed with error"
|
||||||
|
<< reply->errorString();
|
||||||
}
|
}
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|||||||
@@ -7,17 +7,20 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class Requests : public QObject {
|
class Requests : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Requests(QObject* parent = nullptr);
|
explicit Requests(QObject* parent = nullptr);
|
||||||
|
|
||||||
Q_INVOKABLE void get(const QUrl& url, QJSValue callback, QJSValue onError = QJSValue()) const;
|
Q_INVOKABLE void get(
|
||||||
|
const QUrl& url,
|
||||||
|
QJSValue callback,
|
||||||
|
QJSValue onError = QJSValue()) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkAccessManager* m_manager;
|
QNetworkAccessManager* m_manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
+19
-10
@@ -6,7 +6,13 @@
|
|||||||
|
|
||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
Toast::Toast(const QString& title, const QString& message, const QString& icon, Type type, int timeout, QObject* parent)
|
Toast::Toast(
|
||||||
|
const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
const QString& icon,
|
||||||
|
Type type,
|
||||||
|
int timeout,
|
||||||
|
QObject* parent)
|
||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
, m_closed(false)
|
, m_closed(false)
|
||||||
, m_title(title)
|
, m_title(title)
|
||||||
@@ -94,22 +100,25 @@ void Toast::unlock(QObject* sender) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Toaster::Toaster(QObject* parent)
|
Toaster::Toaster(QObject* parent) : QObject(parent) {}
|
||||||
: QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlListProperty<Toast> Toaster::toasts() {
|
QQmlListProperty<Toast> Toaster::toasts() {
|
||||||
return QQmlListProperty<Toast>(this, &m_toasts);
|
return QQmlListProperty<Toast>(this, &m_toasts);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Toaster::toast(const QString& title, const QString& message, const QString& icon, Toast::Type type, int timeout) {
|
void Toaster::toast(
|
||||||
|
const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
const QString& icon,
|
||||||
|
Toast::Type type,
|
||||||
|
int timeout) {
|
||||||
auto* toast = new Toast(title, message, icon, type, timeout, this);
|
auto* toast = new Toast(title, message, icon, type, timeout, this);
|
||||||
QObject::connect(toast, &Toast::finishedClose, this, [toast, this]() {
|
QObject::connect(toast, &Toast::finishedClose, this, [toast, this]() {
|
||||||
if (m_toasts.removeOne(toast)) {
|
if (m_toasts.removeOne(toast)) {
|
||||||
emit toastsChanged();
|
emit toastsChanged();
|
||||||
toast->deleteLater();
|
toast->deleteLater();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
m_toasts.push_front(toast);
|
m_toasts.push_front(toast);
|
||||||
emit toastsChanged();
|
emit toastsChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-52
@@ -8,75 +8,80 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class Toast : public QObject {
|
class Toast : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("Toast instances can only be retrieved from a Toaster")
|
QML_UNCREATABLE("Toast instances can only be retrieved from a Toaster")
|
||||||
|
|
||||||
Q_PROPERTY(bool closed READ closed NOTIFY closedChanged)
|
Q_PROPERTY(bool closed READ closed NOTIFY closedChanged)
|
||||||
Q_PROPERTY(QString title READ title CONSTANT)
|
Q_PROPERTY(QString title READ title CONSTANT)
|
||||||
Q_PROPERTY(QString message READ message CONSTANT)
|
Q_PROPERTY(QString message READ message CONSTANT)
|
||||||
Q_PROPERTY(QString icon READ icon CONSTANT)
|
Q_PROPERTY(QString icon READ icon CONSTANT)
|
||||||
Q_PROPERTY(int timeout READ timeout CONSTANT)
|
Q_PROPERTY(int timeout READ timeout CONSTANT)
|
||||||
Q_PROPERTY(Type type READ type CONSTANT)
|
Q_PROPERTY(Type type READ type CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum class Type {
|
enum class Type { Info = 0, Success, Warning, Error };
|
||||||
Info = 0,
|
Q_ENUM(Type)
|
||||||
Success,
|
|
||||||
Warning,
|
|
||||||
Error
|
|
||||||
};
|
|
||||||
Q_ENUM(Type)
|
|
||||||
|
|
||||||
explicit Toast(const QString& title, const QString& message, const QString& icon, Type type, int timeout,
|
explicit Toast(
|
||||||
QObject* parent = nullptr);
|
const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
const QString& icon,
|
||||||
|
Type type,
|
||||||
|
int timeout,
|
||||||
|
QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] bool closed() const;
|
[[nodiscard]] bool closed() const;
|
||||||
[[nodiscard]] QString title() const;
|
[[nodiscard]] QString title() const;
|
||||||
[[nodiscard]] QString message() const;
|
[[nodiscard]] QString message() const;
|
||||||
[[nodiscard]] QString icon() const;
|
[[nodiscard]] QString icon() const;
|
||||||
[[nodiscard]] int timeout() const;
|
[[nodiscard]] int timeout() const;
|
||||||
[[nodiscard]] Type type() const;
|
[[nodiscard]] Type type() const;
|
||||||
|
|
||||||
Q_INVOKABLE void close();
|
Q_INVOKABLE void close();
|
||||||
Q_INVOKABLE void lock(QObject* sender);
|
Q_INVOKABLE void lock(QObject* sender);
|
||||||
Q_INVOKABLE void unlock(QObject* sender);
|
Q_INVOKABLE void unlock(QObject* sender);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void closedChanged();
|
void closedChanged();
|
||||||
void finishedClose();
|
void finishedClose();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSet<QObject*> m_locks;
|
QSet<QObject*> m_locks;
|
||||||
|
|
||||||
bool m_closed;
|
bool m_closed;
|
||||||
QString m_title;
|
QString m_title;
|
||||||
QString m_message;
|
QString m_message;
|
||||||
QString m_icon;
|
QString m_icon;
|
||||||
Type m_type;
|
Type m_type;
|
||||||
int m_timeout;
|
int m_timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Toaster : public QObject {
|
class Toaster : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
Q_PROPERTY(QQmlListProperty<ZShell::Toast> toasts READ toasts NOTIFY toastsChanged)
|
Q_PROPERTY(
|
||||||
|
QQmlListProperty<ZShell::Toast> toasts READ toasts NOTIFY toastsChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Toaster(QObject* parent = nullptr);
|
explicit Toaster(QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<Toast> toasts();
|
[[nodiscard]] QQmlListProperty<Toast> toasts();
|
||||||
|
|
||||||
Q_INVOKABLE void toast(const QString& title, const QString& message, const QString& icon = QString(),
|
Q_INVOKABLE void toast(
|
||||||
ZShell::Toast::Type type = Toast::Type::Info, int timeout = 5000);
|
const QString& title,
|
||||||
|
const QString& message,
|
||||||
|
const QString& icon = QString(),
|
||||||
|
ZShell::Toast::Type type = Toast::Type::Info,
|
||||||
|
int timeout = 5000);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void toastsChanged();
|
void toastsChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<Toast*> m_toasts;
|
QList<Toast*> m_toasts;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
+125
-137
@@ -20,15 +20,12 @@
|
|||||||
|
|
||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// saveItem
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) {
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) {
|
||||||
this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
|
this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) {
|
void ZShellIo::saveItem(
|
||||||
|
QQuickItem* target, const QUrl& path, const QRect& rect) {
|
||||||
this->saveItem(target, path, rect, QJSValue(), QJSValue());
|
this->saveItem(target, path, rect, QJSValue(), QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +33,13 @@ void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved)
|
|||||||
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
void ZShellIo::saveItem(
|
||||||
|
QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
||||||
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
void ZShellIo::saveItem(
|
||||||
|
QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
||||||
this->saveItem(target, path, rect, onSaved, QJSValue());
|
this->saveItem(target, path, rect, onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +48,7 @@ void ZShellIo::saveItem(
|
|||||||
const QUrl& path,
|
const QUrl& path,
|
||||||
const QRect& rect,
|
const QRect& rect,
|
||||||
QJSValue onSaved,
|
QJSValue onSaved,
|
||||||
QJSValue onFailed
|
QJSValue onFailed) {
|
||||||
) {
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
qWarning() << "ZShellIo::saveItem: a target is required";
|
qWarning() << "ZShellIo::saveItem: a target is required";
|
||||||
return;
|
return;
|
||||||
@@ -62,9 +60,8 @@ void ZShellIo::saveItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!target->window()) {
|
if (!target->window()) {
|
||||||
qWarning() << "ZShellIo::saveItem: unable to save target"
|
qWarning() << "ZShellIo::saveItem: unable to save target" << target
|
||||||
<< target
|
<< "without a window";
|
||||||
<< "without a window";
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +71,11 @@ void ZShellIo::saveItem(
|
|||||||
|
|
||||||
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
||||||
scaledRect = QRectF(
|
scaledRect = QRectF(
|
||||||
rect.left() * scale,
|
rect.left() * scale,
|
||||||
rect.top() * scale,
|
rect.top() * scale,
|
||||||
rect.width() * scale,
|
rect.width() * scale,
|
||||||
rect.height() * scale
|
rect.height() * scale)
|
||||||
).toRect();
|
.toRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QSharedPointer<const QQuickItemGrabResult> grabResult =
|
const QSharedPointer<const QQuickItemGrabResult> grabResult =
|
||||||
@@ -89,66 +86,62 @@ void ZShellIo::saveItem(
|
|||||||
&QQuickItemGrabResult::ready,
|
&QQuickItemGrabResult::ready,
|
||||||
this,
|
this,
|
||||||
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
||||||
const auto future = QtConcurrent::run([grabResult, scaledRect, path]() {
|
const auto future =
|
||||||
QImage image = grabResult->image();
|
QtConcurrent::run([grabResult, scaledRect, path]() {
|
||||||
|
QImage image = grabResult->image();
|
||||||
|
|
||||||
if (scaledRect.isValid()) {
|
if (scaledRect.isValid()) {
|
||||||
image = image.copy(scaledRect);
|
image = image.copy(scaledRect);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString file = path.toLocalFile();
|
const QString file = path.toLocalFile();
|
||||||
const QString parent = QFileInfo(file).absolutePath();
|
const QString parent = QFileInfo(file).absolutePath();
|
||||||
|
|
||||||
QDir().mkpath(parent);
|
QDir().mkpath(parent);
|
||||||
|
|
||||||
QSaveFile out(file);
|
QSaveFile out(file);
|
||||||
if (!out.open(QIODevice::WriteOnly)) {
|
if (!out.open(QIODevice::WriteOnly)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!image.save(&out, "PNG")) {
|
if (!image.save(&out, "PNG")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.commit();
|
return out.commit();
|
||||||
});
|
});
|
||||||
|
|
||||||
auto* watcher = new QFutureWatcher<bool>(this);
|
auto* watcher = new QFutureWatcher<bool>(this);
|
||||||
auto* engine = qmlEngine(this);
|
auto* engine = qmlEngine(this);
|
||||||
|
|
||||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
QObject::connect(
|
||||||
if (watcher->result()) {
|
watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
||||||
if (onSaved.isCallable() && engine) {
|
if (watcher->result()) {
|
||||||
onSaved.call({
|
if (onSaved.isCallable() && engine) {
|
||||||
engine->toScriptValue(path.toLocalFile()),
|
onSaved.call(
|
||||||
engine->toScriptValue(path)
|
{engine->toScriptValue(path.toLocalFile()),
|
||||||
});
|
engine->toScriptValue(path)});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qWarning()
|
||||||
|
<< "ZShellIo::saveItem: failed to save" << path;
|
||||||
|
if (onFailed.isCallable() && engine) {
|
||||||
|
onFailed.call({engine->toScriptValue(path)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
watcher->deleteLater();
|
||||||
qWarning() << "ZShellIo::saveItem: failed to save" << path;
|
});
|
||||||
if (onFailed.isCallable() && engine) {
|
|
||||||
onFailed.call({
|
|
||||||
engine->toScriptValue(path)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watcher->deleteLater();
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// cacheImage
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) {
|
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) {
|
||||||
this->cacheImage(source, cacheDir, QJSValue(), QJSValue());
|
this->cacheImage(source, cacheDir, QJSValue(), QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved) {
|
void ZShellIo::cacheImage(
|
||||||
|
const QUrl& source, const QString& cacheDir, QJSValue onSaved) {
|
||||||
this->cacheImage(source, cacheDir, onSaved, QJSValue());
|
this->cacheImage(source, cacheDir, onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +149,7 @@ void ZShellIo::cacheImage(
|
|||||||
const QUrl& source,
|
const QUrl& source,
|
||||||
const QString& cacheDir,
|
const QString& cacheDir,
|
||||||
QJSValue onSaved,
|
QJSValue onSaved,
|
||||||
QJSValue onFailed
|
QJSValue onFailed) {
|
||||||
) {
|
|
||||||
if (cacheDir.isEmpty()) {
|
if (cacheDir.isEmpty()) {
|
||||||
qWarning() << "ZShellIo::cacheImage: cacheDir is empty";
|
qWarning() << "ZShellIo::cacheImage: cacheDir is empty";
|
||||||
return;
|
return;
|
||||||
@@ -165,92 +157,87 @@ void ZShellIo::cacheImage(
|
|||||||
|
|
||||||
QImage image;
|
QImage image;
|
||||||
if (!loadSourceImage(source, image)) {
|
if (!loadSourceImage(source, image)) {
|
||||||
qWarning() << "ZShellIo::cacheImage: failed to load source image" << source;
|
qWarning() << "ZShellIo::cacheImage: failed to load source image"
|
||||||
|
<< source;
|
||||||
auto* engine = qmlEngine(this);
|
auto* engine = qmlEngine(this);
|
||||||
if (onFailed.isCallable() && engine) {
|
if (onFailed.isCallable() && engine) {
|
||||||
onFailed.call({
|
onFailed.call(
|
||||||
engine->toScriptValue(source),
|
{engine->toScriptValue(source),
|
||||||
engine->toScriptValue(cacheDir)
|
engine->toScriptValue(cacheDir)});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto future = QtConcurrent::run([image, cacheDir]() -> QString {
|
const auto future = QtConcurrent::run([image, cacheDir]() -> QString {
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QImage normalized = image.convertToFormat(QImage::Format_RGBA8888);
|
const QImage normalized =
|
||||||
|
image.convertToFormat(QImage::Format_RGBA8888);
|
||||||
|
|
||||||
const QByteArray bytes(
|
const QByteArray bytes(
|
||||||
reinterpret_cast<const char*>(normalized.constBits()),
|
reinterpret_cast<const char*>(normalized.constBits()),
|
||||||
qsizetype(normalized.sizeInBytes())
|
normalized.sizeInBytes());
|
||||||
);
|
|
||||||
|
|
||||||
const QByteArray digest =
|
const QByteArray digest =
|
||||||
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex();
|
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex();
|
||||||
|
|
||||||
QDir dir(cacheDir);
|
QDir dir(cacheDir);
|
||||||
if (!dir.exists() && !QDir().mkpath(cacheDir)) {
|
if (!dir.exists() && !QDir().mkpath(cacheDir)) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString finalPath = dir.filePath(QString::fromLatin1(digest) + ".png");
|
const QString finalPath =
|
||||||
|
dir.filePath(QString::fromLatin1(digest) + ".png");
|
||||||
if (QFile::exists(finalPath)) {
|
|
||||||
return finalPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSaveFile out(finalPath);
|
|
||||||
if (!out.open(QIODevice::WriteOnly)) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!normalized.save(&out, "PNG")) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!out.commit()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (QFile::exists(finalPath)) {
|
||||||
return finalPath;
|
return finalPath;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
QSaveFile out(finalPath);
|
||||||
|
if (!out.open(QIODevice::WriteOnly)) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalized.save(&out, "PNG")) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.commit()) {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalPath;
|
||||||
|
});
|
||||||
|
|
||||||
auto* watcher = new QFutureWatcher<QString>(this);
|
auto* watcher = new QFutureWatcher<QString>(this);
|
||||||
auto* engine = qmlEngine(this);
|
auto* engine = qmlEngine(this);
|
||||||
|
|
||||||
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() {
|
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() {
|
||||||
const QString finalPath = watcher->result();
|
const QString finalPath = watcher->result();
|
||||||
|
|
||||||
if (!finalPath.isEmpty()) {
|
if (!finalPath.isEmpty()) {
|
||||||
if (onSaved.isCallable() && engine) {
|
if (onSaved.isCallable() && engine) {
|
||||||
onSaved.call({
|
onSaved.call(
|
||||||
engine->toScriptValue(finalPath),
|
{engine->toScriptValue(finalPath),
|
||||||
engine->toScriptValue(QUrl::fromLocalFile(finalPath))
|
engine->toScriptValue(QUrl::fromLocalFile(finalPath))});
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning() << "ZShellIo::cacheImage: failed to cache" << source;
|
|
||||||
if (onFailed.isCallable() && engine) {
|
|
||||||
onFailed.call({
|
|
||||||
engine->toScriptValue(source),
|
|
||||||
engine->toScriptValue(cacheDir)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
qWarning() << "ZShellIo::cacheImage: failed to cache" << source;
|
||||||
|
if (onFailed.isCallable() && engine) {
|
||||||
|
onFailed.call(
|
||||||
|
{engine->toScriptValue(source),
|
||||||
|
engine->toScriptValue(cacheDir)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watcher->deleteLater();
|
watcher->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// loadSourceImage
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
||||||
image = QImage();
|
image = QImage();
|
||||||
|
|
||||||
@@ -270,22 +257,22 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
|||||||
|
|
||||||
const QString providerId = source.host();
|
const QString providerId = source.host();
|
||||||
|
|
||||||
const QString imageId =
|
const QString imageId = source.path().startsWith('/')
|
||||||
source.path().startsWith('/')
|
? source.path().mid(1)
|
||||||
? source.path().mid(1)
|
: source.path();
|
||||||
: source.path();
|
|
||||||
|
|
||||||
auto* providerBase = engine->imageProvider(providerId);
|
auto* providerBase = engine->imageProvider(providerId);
|
||||||
if (!providerBase) {
|
if (!providerBase) {
|
||||||
qWarning() << "ZShellIo::loadSourceImage: provider not found"
|
qWarning() << "ZShellIo::loadSourceImage: provider not found"
|
||||||
<< providerId;
|
<< providerId;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* provider = dynamic_cast<QQuickImageProvider*>(providerBase);
|
auto* provider = dynamic_cast<QQuickImageProvider*>(providerBase);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
qWarning() << "ZShellIo::loadSourceImage: provider is not a QQuickImageProvider"
|
qWarning() << "ZShellIo::loadSourceImage: provider is not a "
|
||||||
<< providerId;
|
"QQuickImageProvider"
|
||||||
|
<< providerId;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +289,7 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
qWarning() << "ZShellIo::loadSourceImage: unsupported provider type"
|
qWarning() << "ZShellIo::loadSourceImage: unsupported provider type"
|
||||||
<< providerId;
|
<< providerId;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,17 +300,16 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
bool ZShellIo::copyFile(
|
||||||
// File ops
|
const QUrl& source, const QUrl& target, bool overwrite) const {
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
|
||||||
if (!source.isLocalFile()) {
|
if (!source.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file";
|
qWarning() << "ZShellIo::copyFile: source" << source
|
||||||
|
<< "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!target.isLocalFile()) {
|
if (!target.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file";
|
qWarning() << "ZShellIo::copyFile: target" << target
|
||||||
|
<< "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +322,8 @@ bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite)
|
|||||||
|
|
||||||
bool ZShellIo::deleteFile(const QUrl& path) const {
|
bool ZShellIo::deleteFile(const QUrl& path) const {
|
||||||
if (!path.isLocalFile()) {
|
if (!path.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file";
|
qWarning() << "ZShellIo::deleteFile: path" << path
|
||||||
|
<< "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +332,8 @@ bool ZShellIo::deleteFile(const QUrl& path) const {
|
|||||||
|
|
||||||
QString ZShellIo::toLocalFile(const QUrl& url) const {
|
QString ZShellIo::toLocalFile(const QUrl& url) const {
|
||||||
if (!url.isLocalFile()) {
|
if (!url.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url;
|
qWarning() << "ZShellIo::toLocalFile: given url is not a local file"
|
||||||
|
<< url;
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,31 +10,31 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class ZShellIo : public QObject {
|
class ZShellIo : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_SINGLETON
|
||||||
|
|
||||||
Q_OBJECT
|
public:
|
||||||
QML_ELEMENT
|
// clang-format off
|
||||||
QML_SINGLETON
|
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);
|
||||||
|
|
||||||
public:
|
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir);
|
||||||
// clang-format off
|
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
// clang-format on
|
||||||
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);
|
|
||||||
|
|
||||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir);
|
Q_INVOKABLE [[nodiscard]] bool copyFile(
|
||||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved);
|
const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
||||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed);
|
Q_INVOKABLE [[nodiscard]] bool deleteFile(const QUrl& path) const;
|
||||||
// clang-format on
|
Q_INVOKABLE [[nodiscard]] QString toLocalFile(const QUrl& url) const;
|
||||||
|
|
||||||
Q_INVOKABLE [[nodiscard]] bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
private:
|
||||||
Q_INVOKABLE [[nodiscard]] bool deleteFile(const QUrl& path) const;
|
bool loadSourceImage(const QUrl& source, QImage& image) const;
|
||||||
Q_INVOKABLE [[nodiscard]] QString toLocalFile(const QUrl& url) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool loadSourceImage(const QUrl& source, QImage& image) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
+58
-32
@@ -27,15 +27,22 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) {
|
|||||||
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
void ZUtils::saveItem(
|
||||||
|
QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
||||||
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
void ZUtils::saveItem(
|
||||||
|
QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
||||||
this->saveItem(target, path, rect, onSaved, QJSValue());
|
this->saveItem(target, path, rect, onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed) {
|
void ZUtils::saveItem(
|
||||||
|
QQuickItem* target,
|
||||||
|
const QUrl& path,
|
||||||
|
const QRect& rect,
|
||||||
|
QJSValue onSaved,
|
||||||
|
QJSValue onFailed) {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
qCWarning(lcZUtils) << "saveItem: a target is required";
|
qCWarning(lcZUtils) << "saveItem: a target is required";
|
||||||
return;
|
return;
|
||||||
@@ -47,21 +54,30 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!target->window()) {
|
if (!target->window()) {
|
||||||
qCWarning(lcZUtils) << "saveItem: unable to save target" << target << "without a window";
|
qCWarning(lcZUtils) << "saveItem: unable to save target" << target
|
||||||
|
<< "without a window";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto scaledRect = rect;
|
auto scaledRect = rect;
|
||||||
const qreal scale = target->window()->devicePixelRatio();
|
const qreal scale = target->window()->devicePixelRatio();
|
||||||
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
||||||
scaledRect =
|
scaledRect = QRectF(
|
||||||
QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect();
|
rect.left() * scale,
|
||||||
|
rect.top() * scale,
|
||||||
|
rect.width() * scale,
|
||||||
|
rect.height() * scale)
|
||||||
|
.toRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage();
|
const QSharedPointer<const QQuickItemGrabResult> grabResult =
|
||||||
|
target->grabToImage();
|
||||||
|
|
||||||
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this,
|
QObject::connect(
|
||||||
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
grabResult.data(),
|
||||||
|
&QQuickItemGrabResult::ready,
|
||||||
|
this,
|
||||||
|
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
||||||
const auto future = QtConcurrent::run([=]() {
|
const auto future = QtConcurrent::run([=]() {
|
||||||
QImage image = grabResult->image();
|
QImage image = grabResult->image();
|
||||||
|
|
||||||
@@ -77,44 +93,52 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
|
|||||||
auto* watcher = new QFutureWatcher<bool>(this);
|
auto* watcher = new QFutureWatcher<bool>(this);
|
||||||
auto* engine = qmlEngine(this);
|
auto* engine = qmlEngine(this);
|
||||||
|
|
||||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
QObject::connect(
|
||||||
if (watcher->result()) {
|
watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
||||||
if (onSaved.isCallable()) {
|
if (watcher->result()) {
|
||||||
QJSValueList args = { QJSValue(path.toLocalFile()) };
|
if (onSaved.isCallable()) {
|
||||||
if (engine) {
|
QJSValueList args = {QJSValue(path.toLocalFile())};
|
||||||
args << engine->toScriptValue(QVariant::fromValue(path));
|
if (engine) {
|
||||||
|
args << engine->toScriptValue(
|
||||||
|
QVariant::fromValue(path));
|
||||||
|
}
|
||||||
|
onSaved.call(args);
|
||||||
}
|
}
|
||||||
onSaved.call(args);
|
} else {
|
||||||
}
|
qCWarning(lcZUtils)
|
||||||
} else {
|
<< "saveItem: failed to save" << path;
|
||||||
qCWarning(lcZUtils) << "saveItem: failed to save" << path;
|
if (onFailed.isCallable()) {
|
||||||
if (onFailed.isCallable()) {
|
if (engine) {
|
||||||
if (engine) {
|
onFailed.call({engine->toScriptValue(
|
||||||
onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) });
|
QVariant::fromValue(path))});
|
||||||
} else {
|
} else {
|
||||||
onFailed.call();
|
onFailed.call();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
watcher->deleteLater();
|
||||||
watcher->deleteLater();
|
});
|
||||||
});
|
|
||||||
watcher->setFuture(future);
|
watcher->setFuture(future);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) {
|
bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) {
|
||||||
if (!source.isLocalFile()) {
|
if (!source.isLocalFile()) {
|
||||||
qCWarning(lcZUtils) << "copyFile: source" << source << "is not a local file";
|
qCWarning(lcZUtils)
|
||||||
|
<< "copyFile: source" << source << "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!target.isLocalFile()) {
|
if (!target.isLocalFile()) {
|
||||||
qCWarning(lcZUtils) << "copyFile: target" << target << "is not a local file";
|
qCWarning(lcZUtils)
|
||||||
|
<< "copyFile: target" << target << "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overwrite && QFile::exists(target.toLocalFile())) {
|
if (overwrite && QFile::exists(target.toLocalFile())) {
|
||||||
if (!QFile::remove(target.toLocalFile())) {
|
if (!QFile::remove(target.toLocalFile())) {
|
||||||
qCWarning(lcZUtils) << "copyFile: overwrite was specified but failed to remove" << target.toLocalFile();
|
qCWarning(lcZUtils)
|
||||||
|
<< "copyFile: overwrite was specified but failed to remove"
|
||||||
|
<< target.toLocalFile();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +148,8 @@ bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) {
|
|||||||
|
|
||||||
bool ZUtils::deleteFile(const QUrl& path) {
|
bool ZUtils::deleteFile(const QUrl& path) {
|
||||||
if (!path.isLocalFile()) {
|
if (!path.isLocalFile()) {
|
||||||
qCWarning(lcZUtils) << "deleteFile: path" << path << "is not a local file";
|
qCWarning(lcZUtils)
|
||||||
|
<< "deleteFile: path" << path << "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +158,8 @@ bool ZUtils::deleteFile(const QUrl& path) {
|
|||||||
|
|
||||||
QString ZUtils::toLocalFile(const QUrl& url) {
|
QString ZUtils::toLocalFile(const QUrl& url) {
|
||||||
if (!url.isLocalFile()) {
|
if (!url.isLocalFile()) {
|
||||||
qCWarning(lcZUtils) << "toLocalFile: given url is not a local file" << url;
|
qCWarning(lcZUtils)
|
||||||
|
<< "toLocalFile: given url is not a local file" << url;
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-18
@@ -8,28 +8,29 @@
|
|||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class ZUtils : public QObject {
|
class ZUtils : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
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, 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);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
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);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
Q_INVOKABLE static bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true);
|
Q_INVOKABLE static bool copyFile(
|
||||||
Q_INVOKABLE static bool deleteFile(const QUrl& path);
|
const QUrl& source, const QUrl& target, bool overwrite = true);
|
||||||
Q_INVOKABLE static QString toLocalFile(const QUrl& url);
|
Q_INVOKABLE static bool deleteFile(const QUrl& path);
|
||||||
|
Q_INVOKABLE static QString toLocalFile(const QUrl& url);
|
||||||
|
|
||||||
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
|
Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max);
|
||||||
|
|
||||||
[[nodiscard]] QString version() const;
|
[[nodiscard]] QString version() const;
|
||||||
[[nodiscard]] QString qtVersion() const;
|
[[nodiscard]] QString qtVersion() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
Reference in New Issue
Block a user