#pragma once #include #include #include #include #include #include #include #include #include #include namespace ZShell::components { class CarouselView : public QQuickItem { Q_OBJECT QML_ELEMENT Q_PROPERTY( QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) Q_PROPERTY(QVariantList model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY( qreal focalWidth READ focalWidth WRITE setFocalWidth NOTIFY focalWidthChanged) Q_PROPERTY( qreal minEdgeWidth READ minEdgeWidth WRITE setMinEdgeWidth NOTIFY minEdgeWidthChanged) Q_PROPERTY( qreal itemSpacing READ itemSpacing WRITE setItemSpacing NOTIFY itemSpacingChanged) Q_PROPERTY( qreal tileHeight READ tileHeight WRITE setTileHeight NOTIFY tileHeightChanged) Q_PROPERTY( qreal maxWidth READ maxWidth WRITE setMaxWidth NOTIFY maxWidthChanged) Q_PROPERTY( int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) Q_PROPERTY( int glideDuration READ glideDuration WRITE setGlideDuration NOTIFY glideDurationChanged) Q_PROPERTY( QEasingCurve::Type glideEasingType READ glideEasingType WRITE setGlideEasingType NOTIFY glideEasingTypeChanged) // Extra properties (e.g. { visibilities: root.visibilities }) forwarded // verbatim onto every delegate instance as a property of the same name. Q_PROPERTY( QVariantMap delegateProperties READ delegateProperties WRITE setDelegateProperties NOTIFY delegatePropertiesChanged) public: explicit CarouselView(QQuickItem* parent = nullptr); ~CarouselView() override; QQmlComponent* delegate() const { return m_delegate; } void setDelegate(QQmlComponent* c); QVariantList model() const { return m_model; } void setModel(const QVariantList& m); qreal focalWidth() const { return m_focalWidth; } void setFocalWidth(qreal v); qreal minEdgeWidth() const { return m_minEdgeWidth; } void setMinEdgeWidth(qreal v); qreal itemSpacing() const { return m_itemSpacing; } void setItemSpacing(qreal v); qreal tileHeight() const { return m_tileHeight; } void setTileHeight(qreal v); qreal maxWidth() const { return m_maxWidth; } void setMaxWidth(qreal v); int currentIndex() const { return m_currentRealIndex; } void setCurrentIndex(int idx); // jumps (animated glide) to a real index int glideDuration() const { return m_glideDuration; } void setGlideDuration(int v); QEasingCurve::Type glideEasingType() const { return m_glideEasingType; } void setGlideEasingType(QEasingCurve::Type t); QVariantMap delegateProperties() const { return m_delegateProperties; } void setDelegateProperties(const QVariantMap& v); // Animated (glides). Q_INVOKABLE void goToIndex(int realIndex); Q_INVOKABLE void incrementCurrentIndex(); Q_INVOKABLE void decrementCurrentIndex(); // Instant, no animation -- use for initial placement (e.g. restoring the // saved wallpaper on Component.onCompleted) where a glide would be wrong. Q_INVOKABLE void jumpToIndex(int realIndex); signals: void delegateChanged(); void modelChanged(); void focalWidthChanged(); void minEdgeWidthChanged(); void itemSpacingChanged(); void tileHeightChanged(); void maxWidthChanged(); void currentIndexChanged(); void glideDurationChanged(); void glideEasingTypeChanged(); void delegatePropertiesChanged(); // Emitted when the currently-centred delegate is clicked while already // centred (i.e. "activate/select this one"). void activated(int realIndex, QVariant modelData); // Emitted continuously as the centred item changes, for preview-on-scroll // behaviour (used to drive Wallpapers.preview() from QML). void previewIndexChanged(int realIndex, QVariant modelData); protected: void geometryChange(const QRectF& newGeo, const QRectF& oldGeo) override; void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void mouseUngrabEvent() override; void wheelEvent(QWheelEvent* event) override; private slots: void handleDelegateActivate(); private: struct Arrangement { int n = 0; qreal width = 0; QVector sizes; // sizes[0] = focal, sizes[i] = i-th side item QVector centers; // centers[0] = 0, centers[i] = i-th side centre offset qreal effectiveExtraWidth = 0; }; struct Slot { QQuickItem* item = nullptr; int virtualIndex = 0; // which "virtual" (unbounded) index this slot currently renders int realIndex = -1; // resolved model index, -1 if none / hidden bool active = false; }; void rebuildArrangement(); void relayout(); // recomputes window + positions every slot (one pass) void updateSlotContent(Slot& slot, int virtualIndex); void positionSlot(const Slot& slot) const; void applyPropertiesToItem(QQuickItem* item, int realIndex) const; qreal pitch() const { return m_focalWidth + m_itemSpacing; } qreal contentXForIndex(qreal index) const; int indexForContentX(qreal x) const; qreal snapTargetX(qreal x) const; void snapToNearest(); void glideTo(qreal dest); void cancelAnimations(); qreal keylineCenter(int io) const; qreal keylineSize(int io) const; qreal sampleCenter(qreal childLoc) const; qreal sampleSize(qreal childLoc) const; int realIndexOf(int virtualIndex) const; void updateCurrentIndexFromContentX(bool emitPreview); void wrapContentIfNeeded(); void centerInstantlyOnReal(int realIndex); // no animation, no glide QQuickItem* acquireItem(); void releaseAllItems(); QQmlComponent* m_delegate = nullptr; QVariantList m_model; QVariantMap m_delegateProperties; qreal m_focalWidth = 0; qreal m_minEdgeWidth = 56; qreal m_itemSpacing = 8; qreal m_tileHeight = 0; qreal m_maxWidth = 0; Arrangement m_arrangement; qreal m_contentX = 0; int m_currentVirtualIndex = 0; int m_currentRealIndex = -1; int m_previewRealIndex = -1; static constexpr int kLoopMultiplier = 401; int m_centerBlockStart = 0; QList m_slots; // pooled delegate slots, unordered; window recomputed each relayout int m_glideDuration = 250; QEasingCurve::Type m_glideEasingType = QEasingCurve::OutCubic; QVariantAnimation* m_glideAnim = nullptr; // Drag / flick state bool m_dragging = false; bool m_dragActive = false; // exceeded the drag threshold QPointF m_pressPos; qreal m_pressContentX = 0; QElapsedTimer m_dragTimer; qreal m_lastMoveX = 0; qint64 m_lastMoveT = 0; qreal m_velocity = 0; // px/sec QVariantAnimation* m_flickAnim = nullptr; bool m_initializedLayout = false; bool m_relayoutQueued = false; }; }; // namespace ZShell::components