#pragma once #include #include #include #include #include #include #include #include #include #include #include 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 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& 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 m_layout; QHash m_delegates; QHash m_itemToIndex; QVector m_dyingDelegates; QVector m_measureQueue; QTimer* m_measureTimer = nullptr; bool m_componentComplete = false; bool m_relayoutPending = false; bool m_animateY = false; QPointer m_anchorItem; Edge m_anchorEdge = TopEdge; int m_restoreIndex = -1; bool m_restoreActive = false; qint64 m_restoreQuietUntil = 0; QList m_modelConnections; }; } // namespace ZShell::components