feat: better notification storage and image caching + blobs
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 11s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 12s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Failing after 27s
Rust / build (pull_request) Successful in 54s
Python / verify (pull_request) Failing after 1m26s
Rust / fmt (pull_request) Successful in 46s
Rust / clippy (pull_request) Successful in 1m6s
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
#include <QQmlComponent>
|
||||
#include <private/qquickflickable_p.h>
|
||||
#include <private/qquickitem_p.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
class QTimer;
|
||||
|
||||
namespace ZShell::components {
|
||||
|
||||
// A ListView replacement that lays delegates out the way SnapLayout lays out
|
||||
// children: delegates are placed on device-pixel boundaries, the gaps between
|
||||
// them are whole device pixels, siblings that move together keep their relative
|
||||
// offsets, and a delegate that only moves is not resized. Like SnapLayout is to
|
||||
// RowLayout/ColumnLayout, this is to ListView: a Flickable with its own
|
||||
// placement, so there is no base-view positioner to fight with.
|
||||
//
|
||||
// Delegates receive `index` and `modelData` initial properties, so a delegate
|
||||
// declares `required property int index` and `required property var modelData`.
|
||||
// The cross-axis size (height in a horizontal view, width in a vertical one) is
|
||||
// set by the view, as ListView does; the main-axis size comes from the delegate
|
||||
// unless snapSizes is true.
|
||||
class SnapView : public QQuickFlickable, public QQuickItemChangeListener {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(
|
||||
QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged
|
||||
FINAL)
|
||||
Q_PROPERTY(
|
||||
QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY
|
||||
delegateChanged FINAL)
|
||||
|
||||
// Layout
|
||||
Q_PROPERTY(
|
||||
Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY
|
||||
orientationChanged FINAL)
|
||||
Q_PROPERTY(
|
||||
qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged FINAL)
|
||||
Q_PROPERTY(
|
||||
qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY
|
||||
cacheBufferChanged FINAL)
|
||||
Q_PROPERTY(
|
||||
qreal estimatedSize READ estimatedSize WRITE setEstimatedSize NOTIFY
|
||||
estimatedSizeChanged FINAL)
|
||||
|
||||
// Resize delegates to device pixels along the main axis too. Off by
|
||||
// default, because delegates usually derive that size from their content
|
||||
// and resizing them would break the binding. Placement is always
|
||||
// device-pixel snapped.
|
||||
Q_PROPERTY(
|
||||
bool snapSizes READ snapSizes WRITE setSnapSizes NOTIFY snapSizesChanged
|
||||
FINAL)
|
||||
|
||||
// Spread delegate creation over frames instead of creating everything in
|
||||
// the first polish pass.
|
||||
Q_PROPERTY(
|
||||
bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY
|
||||
asynchronousChanged FINAL)
|
||||
|
||||
// State
|
||||
Q_PROPERTY(int count READ count NOTIFY countChanged FINAL)
|
||||
Q_PROPERTY(
|
||||
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY
|
||||
currentIndexChanged FINAL)
|
||||
Q_PROPERTY(
|
||||
QQuickItem* currentItem READ currentItem NOTIFY currentItemChanged FINAL)
|
||||
|
||||
signals:
|
||||
void modelChanged();
|
||||
void delegateChanged();
|
||||
void orientationChanged();
|
||||
void spacingChanged();
|
||||
void cacheBufferChanged();
|
||||
void estimatedSizeChanged();
|
||||
void snapSizesChanged();
|
||||
void asynchronousChanged();
|
||||
void countChanged();
|
||||
void currentIndexChanged();
|
||||
void currentItemChanged();
|
||||
|
||||
public:
|
||||
explicit SnapView(QQuickItem* parent = nullptr);
|
||||
~SnapView() override;
|
||||
|
||||
[[nodiscard]] QAbstractItemModel* model() const;
|
||||
void setModel(QAbstractItemModel* model);
|
||||
|
||||
[[nodiscard]] QQmlComponent* delegate() const;
|
||||
void setDelegate(QQmlComponent* delegate);
|
||||
|
||||
[[nodiscard]] Qt::Orientation orientation() const;
|
||||
void setOrientation(Qt::Orientation orientation);
|
||||
|
||||
[[nodiscard]] qreal spacing() const;
|
||||
void setSpacing(qreal spacing);
|
||||
|
||||
[[nodiscard]] qreal cacheBuffer() const;
|
||||
void setCacheBuffer(qreal buffer);
|
||||
|
||||
[[nodiscard]] qreal estimatedSize() const;
|
||||
void setEstimatedSize(qreal size);
|
||||
|
||||
[[nodiscard]] bool snapSizes() const;
|
||||
void setSnapSizes(bool snapSizes);
|
||||
|
||||
[[nodiscard]] bool asynchronous() const;
|
||||
void setAsynchronous(bool asynchronous);
|
||||
|
||||
[[nodiscard]] int count() const;
|
||||
|
||||
[[nodiscard]] int currentIndex() const;
|
||||
void setCurrentIndex(int index);
|
||||
|
||||
[[nodiscard]] QQuickItem* currentItem() const;
|
||||
|
||||
Q_INVOKABLE void positionViewAtBeginning();
|
||||
Q_INVOKABLE void positionViewAtEnd();
|
||||
Q_INVOKABLE void positionViewAtIndex(int index);
|
||||
|
||||
protected:
|
||||
void componentComplete() override;
|
||||
void geometryChange(
|
||||
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
||||
void updatePolish() override;
|
||||
void itemChange(ItemChange change, const ItemChangeData& value) override;
|
||||
|
||||
// QQuickItemChangeListener
|
||||
void itemGeometryChanged(
|
||||
QQuickItem* item,
|
||||
QQuickGeometryChange change,
|
||||
const QRectF& oldGeometry) override;
|
||||
void itemDestroyed(QQuickItem* item) override;
|
||||
|
||||
private:
|
||||
struct Row {
|
||||
qreal exactOffset = 0.0;
|
||||
qreal exactSize = 0.0;
|
||||
};
|
||||
|
||||
// Main-axis size last measured from a delegate, kept per model row so the
|
||||
// content extent stays right while delegates outside the viewport are
|
||||
// destroyed, the way ListView keeps contentHeight.
|
||||
QVector<qreal> m_rowSizes;
|
||||
|
||||
// Placement, mirroring SnapLayout::arrangeChildren: exact positions come
|
||||
// from cumulative row sizes, each near edge is rounded on its own so gaps
|
||||
// stay whole device pixels, and siblings keep their relative offsets.
|
||||
void layoutAndPlace();
|
||||
void updateDelegates();
|
||||
[[nodiscard]] QQuickItem* createDelegate(int index);
|
||||
void destroyDelegate(int index);
|
||||
void updateDelegateData(int index);
|
||||
|
||||
[[nodiscard]] qreal effectiveSpacing() const;
|
||||
[[nodiscard]] qreal devicePixelRatio() const;
|
||||
// Main-axis size of a row: the delegate's own size if it exists, otherwise
|
||||
// the size cached when it was last visible, otherwise the estimate.
|
||||
[[nodiscard]] qreal rowSize(int index) const;
|
||||
[[nodiscard]] qint64 snappedDeviceMainSize(QQuickItem* item);
|
||||
[[nodiscard]] int firstVisibleIndex() const;
|
||||
[[nodiscard]] int lastVisibleIndex() const;
|
||||
[[nodiscard]] QPointF contentOrigin() const;
|
||||
void trackOriginResidual();
|
||||
|
||||
[[nodiscard]] QVariantMap delegateProperties(int index);
|
||||
// The model's role names, cached because some models rebuild the hash on
|
||||
// every call and this runs once per delegate per refresh.
|
||||
[[nodiscard]] const QHash<int, QByteArray>& roleNames();
|
||||
void invalidateRoleNames();
|
||||
|
||||
void rebuildRows();
|
||||
// Sizes are measured along the main axis, so switching axis invalidates
|
||||
// every cached size.
|
||||
void invalidateMainAxisCaches();
|
||||
// Re-keys the delegate map when rows shift, so an existing item keeps its
|
||||
// place in the model instead of pointing at the wrong row.
|
||||
void shiftDelegateIndices(int from, int delta);
|
||||
void updateAncestorListeners();
|
||||
void clearListeners();
|
||||
void connectModel();
|
||||
void disconnectModel();
|
||||
|
||||
void onRowsInserted(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 onDataChanged(
|
||||
const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
void onModelReset();
|
||||
|
||||
QPointer<QAbstractItemModel> m_model;
|
||||
QPointer<QQmlComponent> m_delegate;
|
||||
|
||||
Qt::Orientation m_orientation = Qt::Vertical;
|
||||
qreal m_spacing = 0.0;
|
||||
qreal m_cacheBuffer = 0.0;
|
||||
qreal m_estimatedSize = -1.0;
|
||||
bool m_snapSizes = false;
|
||||
bool m_asynchronous = false;
|
||||
|
||||
int m_currentIndex = -1;
|
||||
|
||||
QVector<Row> m_rows;
|
||||
QHash<int, QQuickItem*> m_items;
|
||||
QVector<int> m_pendingCreate;
|
||||
|
||||
// Device-pixel main size of a delegate, so an item that only moves keeps
|
||||
// the size it already has rather than re-rounding a drifting exact size.
|
||||
QHash<QQuickItem*, qint64> m_sizeDevCache;
|
||||
|
||||
// Cached model role names; valid until the model or its roles change.
|
||||
QHash<int, QByteArray> m_roleNames;
|
||||
bool m_roleNamesValid = false;
|
||||
|
||||
// Fires the next asynchronous creation batch on the next event loop turn,
|
||||
// so batches land on different frames instead of one polish flush.
|
||||
QTimer* m_asyncTimer = nullptr;
|
||||
|
||||
// Items watched for sub-pixel moves. QPointer, as SnapLayout does, so a
|
||||
// teardown order that destroys an item before the view cannot leave a
|
||||
// dangling pointer to remove a listener from.
|
||||
QVector<QPointer<QQuickItem>> m_delegateListeners;
|
||||
QVector<QPointer<QQuickItem>> m_ancestorListeners;
|
||||
qint64 m_originResidualX = -1;
|
||||
qint64 m_originResidualY = -1;
|
||||
|
||||
bool m_placing = false;
|
||||
bool m_componentComplete = false;
|
||||
|
||||
QList<QMetaObject::Connection> m_modelConnections;
|
||||
};
|
||||
|
||||
} // namespace ZShell::components
|
||||
Reference in New Issue
Block a user