C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Failing after 11s
JS/TS / lint (pull_request) Successful in 9s
Python / static (pull_request) Failing after 31s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m37s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 58s
300 lines
8.0 KiB
C++
300 lines
8.0 KiB
C++
#pragma once
|
|
|
|
#include <QAbstractItemModel>
|
|
#include <QElapsedTimer>
|
|
#include <QHash>
|
|
#include <QList>
|
|
#include <QObject>
|
|
#include <QPointer>
|
|
#include <QQmlComponent>
|
|
#include <QQuickItem>
|
|
#include <QRect>
|
|
#include <QTimer>
|
|
#include <QVector>
|
|
|
|
namespace ZShell::components {
|
|
|
|
class ChatListViewAttached : public QObject {
|
|
Q_OBJECT
|
|
|
|
Q_PROPERTY(
|
|
qreal preferredHeight READ preferredHeight WRITE setPreferredHeight
|
|
NOTIFY preferredHeightChanged)
|
|
Q_PROPERTY(
|
|
qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY
|
|
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:
|
|
explicit ChatListViewAttached(QObject* parent = nullptr);
|
|
|
|
[[nodiscard]] qreal preferredHeight() const;
|
|
void setPreferredHeight(qreal height);
|
|
|
|
[[nodiscard]] qreal visibleHeight() const;
|
|
void setVisibleHeight(qreal height);
|
|
|
|
[[nodiscard]] bool ready() const;
|
|
void setReady(bool ready);
|
|
|
|
[[nodiscard]] bool adding() const;
|
|
void setAdding(bool adding);
|
|
|
|
[[nodiscard]] bool removing() const;
|
|
void setRemoving(bool removing);
|
|
|
|
[[nodiscard]] bool trackViewport() const;
|
|
void setTrackViewport(bool track);
|
|
|
|
signals:
|
|
void preferredHeightChanged();
|
|
void visibleHeightChanged();
|
|
void readyChanged();
|
|
void addingChanged();
|
|
void removingChanged();
|
|
void trackViewportChanged();
|
|
|
|
private:
|
|
qreal m_preferredHeight = -1;
|
|
qreal m_visibleHeight = -1;
|
|
bool m_ready = false;
|
|
bool m_adding = false;
|
|
bool m_removing = false;
|
|
bool m_trackViewport = false;
|
|
};
|
|
|
|
class ChatListView : public QQuickItem {
|
|
Q_OBJECT
|
|
QML_ELEMENT
|
|
QML_ATTACHED(ChatListViewAttached)
|
|
|
|
public:
|
|
Q_PROPERTY(
|
|
QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged)
|
|
Q_PROPERTY(
|
|
QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY
|
|
delegateChanged)
|
|
|
|
Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
|
Q_PROPERTY(
|
|
qreal contentHeight READ contentHeight NOTIFY contentHeightChanged)
|
|
Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged)
|
|
Q_PROPERTY(
|
|
qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged)
|
|
|
|
Q_PROPERTY(
|
|
QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged)
|
|
Q_PROPERTY(
|
|
bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport
|
|
NOTIFY useCustomViewportChanged)
|
|
Q_PROPERTY(
|
|
qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY
|
|
cacheBufferChanged)
|
|
|
|
Q_PROPERTY(
|
|
qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight
|
|
NOTIFY estimatedHeightChanged)
|
|
|
|
Q_PROPERTY(
|
|
bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY
|
|
asynchronousChanged)
|
|
|
|
Q_PROPERTY(
|
|
int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY
|
|
removeDurationChanged)
|
|
Q_PROPERTY(
|
|
int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY
|
|
readyDelayChanged)
|
|
Q_PROPERTY(
|
|
int yAnimDuration READ yAnimDuration WRITE setYAnimDuration NOTIFY
|
|
yAnimDurationChanged)
|
|
|
|
Q_PROPERTY(int count READ count NOTIFY countChanged)
|
|
|
|
enum Edge {
|
|
TopEdge,
|
|
BottomEdge,
|
|
};
|
|
Q_ENUM(Edge)
|
|
|
|
Q_INVOKABLE void anchorItem(QQuickItem* item, Edge edge);
|
|
Q_INVOKABLE void clearAnchor();
|
|
Q_INVOKABLE void requestRestore(qreal targetY);
|
|
|
|
explicit ChatListView(QQuickItem* parent = nullptr);
|
|
~ChatListView() override;
|
|
|
|
static ChatListViewAttached* qmlAttachedProperties(QObject* object);
|
|
|
|
[[nodiscard]] QAbstractItemModel* model() const;
|
|
void setModel(QAbstractItemModel* model);
|
|
|
|
[[nodiscard]] QQmlComponent* delegate() const;
|
|
void setDelegate(QQmlComponent* delegate);
|
|
|
|
[[nodiscard]] qreal spacing() const;
|
|
void setSpacing(qreal spacing);
|
|
|
|
[[nodiscard]] qreal contentHeight() const;
|
|
[[nodiscard]] qreal layoutHeight() const;
|
|
|
|
[[nodiscard]] qreal contentY() const;
|
|
void setContentY(qreal contentY);
|
|
|
|
[[nodiscard]] QRectF viewport() const;
|
|
void setViewport(const QRectF& viewport);
|
|
|
|
[[nodiscard]] bool useCustomViewport() const;
|
|
void setUseCustomViewport(bool use);
|
|
|
|
[[nodiscard]] qreal cacheBuffer() const;
|
|
void setCacheBuffer(qreal buffer);
|
|
|
|
[[nodiscard]] qreal estimatedHeight() const;
|
|
void setEstimatedHeight(qreal height);
|
|
|
|
[[nodiscard]] bool asynchronous() const;
|
|
void setAsynchronous(bool async);
|
|
|
|
[[nodiscard]] int removeDuration() const;
|
|
void setRemoveDuration(int duration);
|
|
|
|
[[nodiscard]] int readyDelay() const;
|
|
void setReadyDelay(int delay);
|
|
|
|
[[nodiscard]] int yAnimDuration() const;
|
|
void setYAnimDuration(int duration);
|
|
|
|
[[nodiscard]] int count() const;
|
|
|
|
signals:
|
|
void modelChanged();
|
|
void delegateChanged();
|
|
void spacingChanged();
|
|
void contentHeightChanged();
|
|
void layoutHeightChanged();
|
|
void contentYChanged();
|
|
void viewportChanged();
|
|
void useCustomViewportChanged();
|
|
void cacheBufferChanged();
|
|
void estimatedHeightChanged();
|
|
void asynchronousChanged();
|
|
void removeDurationChanged();
|
|
void readyDelayChanged();
|
|
void yAnimDurationChanged();
|
|
void countChanged();
|
|
void viewportAdjustNeeded(qreal delta);
|
|
void viewportRestoreRequested(qreal targetY);
|
|
|
|
protected:
|
|
void componentComplete() override;
|
|
void geometryChange(
|
|
const QRectF& newGeometry, const QRectF& oldGeometry) override;
|
|
void updatePolish() override;
|
|
bool event(QEvent* e) override;
|
|
|
|
private:
|
|
struct ItemRecord {
|
|
qreal targetY = 0;
|
|
qreal height = 0;
|
|
bool heightKnown = false;
|
|
bool isNew = false;
|
|
qreal measuredWidth = -1;
|
|
};
|
|
|
|
struct DelegateEntry {
|
|
int modelIndex = -1;
|
|
QQuickItem* item = nullptr;
|
|
bool pendingRemoval = false;
|
|
bool pendingInsert = false;
|
|
bool readyDelayStarted = false;
|
|
bool measureOnly = false;
|
|
qreal propY = 0;
|
|
bool hasPropY = false;
|
|
qint64 lastPropTime = 0;
|
|
qint64 lastHeightChange = 0;
|
|
};
|
|
|
|
void relayout();
|
|
[[nodiscard]] std::pair<int, int> computeVisibleRange() const;
|
|
[[nodiscard]] QRectF effectiveViewport() const;
|
|
[[nodiscard]] qreal effectiveEstimatedHeight() const;
|
|
[[nodiscard]] static bool isStaleRecord(const ItemRecord& record,
|
|
qreal viewWidth);
|
|
[[nodiscard]] static qreal delegateHeight(QQuickItem* item);
|
|
[[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item);
|
|
[[nodiscard]] static bool isDelegateReady(QQuickItem* item);
|
|
void trackHeight(qreal height);
|
|
void untrackHeight(qreal height);
|
|
|
|
void syncDelegates();
|
|
void pumpMeasureQueue();
|
|
void buildMeasureQueue();
|
|
DelegateEntry createDelegate(int modelIndex);
|
|
void destroyDelegate(DelegateEntry& entry);
|
|
void updateDelegateData(DelegateEntry& entry);
|
|
|
|
void connectModel();
|
|
void disconnectModel();
|
|
void resetContent();
|
|
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,
|
|
const QList<int>& roles);
|
|
void onModelReset();
|
|
|
|
void applyAnchor();
|
|
|
|
QAbstractItemModel* m_model = nullptr;
|
|
QQmlComponent* m_delegate = nullptr;
|
|
|
|
qreal m_spacing = 0;
|
|
qreal m_contentHeight = 0;
|
|
qreal m_layoutHeight = 0;
|
|
qreal m_contentY = 0;
|
|
|
|
QRectF m_viewport;
|
|
bool m_useCustomViewport = false;
|
|
qreal m_cacheBuffer = 0;
|
|
|
|
qreal m_estimatedHeight = -1;
|
|
qreal m_knownHeightSum = 0;
|
|
int m_knownHeightCount = 0;
|
|
bool m_asynchronous = false;
|
|
|
|
int m_removeDuration = 300;
|
|
int m_readyDelay = 0;
|
|
int m_yAnimDuration = 300;
|
|
QElapsedTimer m_clock;
|
|
|
|
QVector<ItemRecord> m_layout;
|
|
QHash<int, DelegateEntry> m_delegates;
|
|
QHash<QQuickItem*, int> m_itemToIndex;
|
|
QVector<DelegateEntry> m_dyingDelegates;
|
|
|
|
QVector<int> m_measureQueue;
|
|
QTimer* m_measureTimer = nullptr;
|
|
|
|
bool m_componentComplete = false;
|
|
bool m_relayoutPending = false;
|
|
bool m_animateY = false;
|
|
|
|
QPointer<QQuickItem> m_anchorItem;
|
|
Edge m_anchorEdge = TopEdge;
|
|
int m_restoreIndex = -1;
|
|
bool m_restoreActive = false;
|
|
qint64 m_restoreQuietUntil = 0;
|
|
|
|
QList<QMetaObject::Connection> m_modelConnections;
|
|
};
|
|
|
|
} // namespace ZShell::components
|