QmlMaterial 0.1.0
Loading...
Searching...
No Matches
flickable.hpp
1#pragma once
2
3#include <QBasicTimer>
4#include <QElapsedTimer>
5#include <QObject>
6#include <QPointer>
7#include <QQmlEngine>
8#include <QQmlListProperty>
9#include <QQuickItem>
10#include <QVector>
11#include <QPointerEvent>
12#include <memory>
13#include <optional>
14#include "qml_material/scrollable/scroll_motion.hpp"
15#include "qml_material/export.hpp"
16
17namespace qml_material
18{
19
20class Flickable;
21
22class QML_MATERIAL_API FlickableVisibleArea : public QObject {
23 Q_OBJECT
24 QML_ANONYMOUS
25
26 Q_PROPERTY(qreal xPosition READ xPosition NOTIFY xPositionChanged FINAL)
27 Q_PROPERTY(qreal yPosition READ yPosition NOTIFY yPositionChanged FINAL)
28 Q_PROPERTY(qreal widthRatio READ widthRatio NOTIFY widthRatioChanged FINAL)
29 Q_PROPERTY(qreal heightRatio READ heightRatio NOTIFY heightRatioChanged FINAL)
30
31public:
32 explicit FlickableVisibleArea(Flickable* parent = nullptr);
33
34 qreal xPosition() const;
35 qreal yPosition() const;
36 qreal widthRatio() const;
37 qreal heightRatio() const;
38
39 auto updateVisible() -> void;
40
41 Q_SIGNAL void xPositionChanged(qreal value);
42 Q_SIGNAL void yPositionChanged(qreal value);
43 Q_SIGNAL void widthRatioChanged(qreal value);
44 Q_SIGNAL void heightRatioChanged(qreal value);
45
46private:
47 QPointer<Flickable> m_flickable;
48 qreal m_xPosition { 0 };
49 qreal m_yPosition { 0 };
50 qreal m_widthRatio { 1 };
51 qreal m_heightRatio { 1 };
52};
53
54class QML_MATERIAL_API Flickable : public QQuickItem {
55 Q_OBJECT
56 QML_NAMED_ELEMENT(Scrollable)
57
58 Q_PROPERTY(
59 qreal contentWidth READ contentWidth WRITE setContentWidth NOTIFY contentWidthChanged)
60 Q_PROPERTY(
61 qreal contentHeight READ contentHeight WRITE setContentHeight NOTIFY contentHeightChanged)
62 Q_PROPERTY(qreal contentX READ contentX WRITE setContentX NOTIFY contentXChanged)
63 Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged)
64 Q_PROPERTY(QQuickItem* contentItem READ contentItem CONSTANT)
65
66 Q_PROPERTY(qreal topMargin READ topMargin WRITE setTopMargin NOTIFY topMarginChanged)
67 Q_PROPERTY(
68 qreal bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY bottomMarginChanged)
69 Q_PROPERTY(qreal originY READ originY NOTIFY originYChanged)
70 Q_PROPERTY(qreal leftMargin READ leftMargin WRITE setLeftMargin NOTIFY leftMarginChanged)
71 Q_PROPERTY(qreal rightMargin READ rightMargin WRITE setRightMargin NOTIFY rightMarginChanged)
72 Q_PROPERTY(qreal originX READ originX NOTIFY originXChanged)
73
74 Q_PROPERTY(qreal horizontalVelocity READ horizontalVelocity NOTIFY horizontalVelocityChanged)
75 Q_PROPERTY(qreal verticalVelocity READ verticalVelocity NOTIFY verticalVelocityChanged)
76 Q_PROPERTY(QPointF dragVelocity READ dragVelocity NOTIFY dragVelocityChanged)
77 Q_PROPERTY(qreal maximumFlickVelocity READ maximumFlickVelocity WRITE setMaximumFlickVelocity
78 NOTIFY maximumFlickVelocityChanged)
79 Q_PROPERTY(qreal flickDeceleration READ flickDeceleration WRITE setFlickDeceleration NOTIFY
80 flickDecelerationChanged)
81 Q_PROPERTY(bool moving READ isMoving NOTIFY movingChanged)
82 Q_PROPERTY(bool movingHorizontally READ isMovingHorizontally NOTIFY movingHorizontallyChanged)
83 Q_PROPERTY(bool movingVertically READ isMovingVertically NOTIFY movingVerticallyChanged)
84 Q_PROPERTY(bool flicking READ isFlicking NOTIFY flickingChanged)
85 Q_PROPERTY(
86 bool flickingHorizontally READ isFlickingHorizontally NOTIFY flickingHorizontallyChanged)
87 Q_PROPERTY(bool flickingVertically READ isFlickingVertically NOTIFY flickingVerticallyChanged)
88 Q_PROPERTY(bool dragging READ isDragging NOTIFY draggingChanged)
89 Q_PROPERTY(
90 bool draggingHorizontally READ isDraggingHorizontally NOTIFY draggingHorizontallyChanged)
91 Q_PROPERTY(bool draggingVertically READ isDraggingVertically NOTIFY draggingVerticallyChanged)
92 Q_PROPERTY(FlickableDirection flickableDirection READ flickableDirection WRITE
93 setFlickableDirection NOTIFY flickableDirectionChanged)
94
95 Q_PROPERTY(bool interactive READ isInteractive WRITE setInteractive NOTIFY interactiveChanged)
96 Q_PROPERTY(int pressDelay READ pressDelay WRITE setPressDelay NOTIFY pressDelayChanged)
97 Q_PROPERTY(bool atXEnd READ isAtXEnd NOTIFY atXEndChanged)
98 Q_PROPERTY(bool atYEnd READ isAtYEnd NOTIFY atYEndChanged)
99 Q_PROPERTY(bool atXBeginning READ isAtXBeginning NOTIFY atXBeginningChanged)
100 Q_PROPERTY(bool atYBeginning READ isAtYBeginning NOTIFY atYBeginningChanged)
101 Q_PROPERTY(FlickableVisibleArea* visibleArea READ visibleArea CONSTANT)
102 Q_PROPERTY(bool pixelAligned READ pixelAligned WRITE setPixelAligned NOTIFY pixelAlignedChanged)
103 Q_PROPERTY(bool synchronousDrag READ synchronousDrag WRITE setSynchronousDrag NOTIFY
104 synchronousDragChanged)
105 Q_PROPERTY(Qt::MouseButtons acceptedButtons READ acceptedButtons WRITE setAcceptedButtons NOTIFY
106 acceptedButtonsChanged FINAL)
107 Q_PROPERTY(InputMaskMode inputMaskMode READ inputMaskMode WRITE setInputMaskMode NOTIFY
108 inputMaskModeChanged FINAL)
109 Q_PROPERTY(QQuickItem* interactionItem READ interactionItem WRITE setInteractionItem RESET
110 resetInteractionItem NOTIFY interactionItemChanged FINAL)
111 Q_PROPERTY(QQmlListProperty<QObject> flickableData READ flickableData)
112 Q_PROPERTY(QQmlListProperty<QQuickItem> flickableChildren READ flickableChildren)
113 Q_CLASSINFO("DefaultProperty", "flickableData")
114
115public:
116 explicit Flickable(QQuickItem* parent = nullptr);
117 ~Flickable() override;
118
119 QQmlListProperty<QObject> flickableData();
120 QQmlListProperty<QQuickItem> flickableChildren();
121
122 enum FlickableDirection
123 {
124 AutoFlickDirection = 0x0,
125 HorizontalFlick = 0x1,
126 VerticalFlick = 0x2,
127 HorizontalAndVerticalFlick = 0x3,
128 AutoFlickIfNeeded = 0xc,
129 };
130 Q_ENUM(FlickableDirection)
131
132 enum InputMaskMode
133 {
134 Viewport,
135 ContentOnly,
136 CustomItem,
137 };
138 Q_ENUM(InputMaskMode)
139
140 qreal contentWidth() const;
141 void setContentWidth(qreal value);
142
143 qreal contentHeight() const;
144 void setContentHeight(qreal value);
145
146 qreal contentX() const;
147 virtual void setContentX(qreal position);
148
149 qreal contentY() const;
150 virtual void setContentY(qreal position);
151
152 qreal topMargin() const;
153 void setTopMargin(qreal value);
154
155 qreal bottomMargin() const;
156 void setBottomMargin(qreal value);
157
158 qreal leftMargin() const;
159 void setLeftMargin(qreal value);
160
161 qreal rightMargin() const;
162 void setRightMargin(qreal value);
163
164 virtual qreal originY() const;
165 virtual qreal originX() const;
166
167 bool isMoving() const;
168 bool isMovingHorizontally() const;
169 bool isMovingVertically() const;
170 bool isFlicking() const;
171 bool isFlickingHorizontally() const;
172 bool isFlickingVertically() const;
173 bool isDragging() const;
174 bool isDraggingHorizontally() const;
175 bool isDraggingVertically() const;
176
177 int pressDelay() const;
178 void setPressDelay(int delay);
179
180 qreal maximumFlickVelocity() const;
181 void setMaximumFlickVelocity(qreal value);
182
183 qreal flickDeceleration() const;
184 void setFlickDeceleration(qreal value);
185
186 bool isInteractive() const;
187 void setInteractive(bool value);
188
189 qreal horizontalVelocity() const;
190 qreal verticalVelocity() const;
191 // Drag input velocity in content coordinates, in logical pixels per second; expires at rest.
192 QPointF dragVelocity() const { return m_dragVelocity; }
193
194 bool isAtXEnd() const;
195 bool isAtXBeginning() const;
196 bool isAtYEnd() const;
197 bool isAtYBeginning() const;
198
199 QQuickItem* contentItem() const;
200
201 FlickableDirection flickableDirection() const;
202 void setFlickableDirection(FlickableDirection direction);
203
204 bool pixelAligned() const;
205 void setPixelAligned(bool align);
206
207 bool synchronousDrag() const;
208 void setSynchronousDrag(bool value);
209
210 Qt::MouseButtons acceptedButtons() const;
211 void setAcceptedButtons(Qt::MouseButtons buttons);
212
213 InputMaskMode inputMaskMode() const;
214 void setInputMaskMode(InputMaskMode mode);
215
216 QQuickItem* interactionItem() const;
217 void setInteractionItem(QQuickItem* item);
218 void resetInteractionItem();
219
220 FlickableVisibleArea* visibleArea();
221
222 Q_INVOKABLE void resizeContent(qreal width, qreal height, QPointF center);
223 Q_INVOKABLE void flick(qreal xVelocity, qreal yVelocity);
224 Q_INVOKABLE void cancelFlick();
225
226 enum class ScrollInput
227 {
228 Direct,
229 Smooth
230 };
231 struct ScrollConsumption {
232 QPointF consumed;
233 QPointF remaining;
234 };
235 // Positive deltas increase content offsets; smooth consumption reserves target distance.
236 ScrollConsumption consumeScroll(QPointF delta, ScrollInput input,
237 Qt::ScrollPhase phase = Qt::NoScrollPhase);
238
239 Q_SIGNAL void contentWidthChanged();
240 Q_SIGNAL void contentHeightChanged();
241 Q_SIGNAL void contentXChanged();
242 Q_SIGNAL void contentYChanged();
243 Q_SIGNAL void topMarginChanged();
244 Q_SIGNAL void bottomMarginChanged();
245 Q_SIGNAL void leftMarginChanged();
246 Q_SIGNAL void rightMarginChanged();
247 Q_SIGNAL void originYChanged();
248 Q_SIGNAL void originXChanged();
249 Q_SIGNAL void movingChanged();
250 Q_SIGNAL void movingHorizontallyChanged();
251 Q_SIGNAL void movingVerticallyChanged();
252 Q_SIGNAL void flickingChanged();
253 Q_SIGNAL void flickingHorizontallyChanged();
254 Q_SIGNAL void flickingVerticallyChanged();
255 Q_SIGNAL void draggingChanged();
256 Q_SIGNAL void draggingHorizontallyChanged();
257 Q_SIGNAL void draggingVerticallyChanged();
258 Q_SIGNAL void horizontalVelocityChanged();
259 Q_SIGNAL void verticalVelocityChanged();
260 Q_SIGNAL void isAtBoundaryChanged();
261 Q_SIGNAL void flickableDirectionChanged();
262 Q_SIGNAL void interactiveChanged();
263 Q_SIGNAL void maximumFlickVelocityChanged();
264 Q_SIGNAL void flickDecelerationChanged();
265 Q_SIGNAL void pressDelayChanged();
266 Q_SIGNAL void movementStarted();
267 Q_SIGNAL void movementEnded();
268 Q_SIGNAL void flickStarted();
269 Q_SIGNAL void flickEnded();
270 Q_SIGNAL void dragStarted();
271 Q_SIGNAL void dragEnded();
272 Q_SIGNAL void dragVelocityChanged();
273 Q_SIGNAL void dragReleased(QPointF velocity);
274 Q_SIGNAL void pixelAlignedChanged();
275 Q_SIGNAL void synchronousDragChanged();
276 Q_SIGNAL void atXEndChanged();
277 Q_SIGNAL void atYEndChanged();
278 Q_SIGNAL void atXBeginningChanged();
279 Q_SIGNAL void atYBeginningChanged();
280 Q_SIGNAL void acceptedButtonsChanged();
281 Q_SIGNAL void inputMaskModeChanged();
282 Q_SIGNAL void interactionItemChanged();
283
284protected:
285 // Commit extent and anchor correction together without cancelling an in-range trajectory.
286 void updateContentGeometry(QSizeF extent, QPointF anchorDelta = {});
287 virtual void scrollInputStarted();
288 virtual void scrollActivityCancelled();
289 void setVerticalScrollTarget(qreal position);
290 auto childMouseEventFilter(QQuickItem* item, QEvent* event) -> bool override;
291 auto mousePressEvent(QMouseEvent* event) -> void override;
292 auto mouseMoveEvent(QMouseEvent* event) -> void override;
293 auto mouseReleaseEvent(QMouseEvent* event) -> void override;
294 auto touchEvent(QTouchEvent* event) -> void override;
295 auto wheelEvent(QWheelEvent* event) -> void override;
296 auto timerEvent(QTimerEvent* event) -> void override;
297 auto updatePolish() -> void override;
298 auto geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) -> void override;
299 auto componentComplete() -> void override;
300 auto mouseUngrabEvent() -> void override;
301 auto touchUngrabEvent() -> void override;
302
303protected:
304 virtual auto minXExtent() const -> qreal;
305 virtual auto minYExtent() const -> qreal;
306 virtual auto maxXExtent() const -> qreal;
307 virtual auto maxYExtent() const -> qreal;
308 auto vWidth() const -> qreal;
309 auto vHeight() const -> qreal;
310 virtual auto viewportMoved(Qt::Orientations orientation) -> void;
311 auto xflick() const -> bool;
312 auto yflick() const -> bool;
313
314private:
315 friend class FlickableVisibleArea;
316 friend class ScrollParticipant;
317
318 enum Axis
319 {
320 HorizontalAxis,
321 VerticalAxis,
322 };
323
324 struct AxisData {
325 qreal position { 0 };
326 qreal viewSize { -1 };
327 qreal startMargin { 0 };
328 qreal endMargin { 0 };
329 qreal pressPos { 0 };
330 qreal pressContentPos { 0 };
331 qreal lastPos { 0 };
332 qreal dragStartOffset { 0 };
333 qreal previousDragDelta { 0 };
334 qreal velocity { 0 };
335 qreal smoothVelocity { 0 };
336 ScrollMotion motion;
337 QVector<QPair<qint64, qreal>> velocityBuffer;
338 bool atEnd { false };
339 bool atBeginning { true };
340 bool moving { false };
341 bool flicking { false };
342 bool dragging { false };
343 bool platformScrolling { false };
344
345 auto resetDrag() -> void;
346 auto addVelocitySample(qint64 timestamp, qreal position, qreal maxVelocity) -> void;
347 auto updateVelocity() -> void;
348 };
349
350 static auto dataAppend(QQmlListProperty<QObject>* property, QObject* object) -> void;
351 static auto dataCount(QQmlListProperty<QObject>* property) -> qsizetype;
352 static auto dataAt(QQmlListProperty<QObject>* property, qsizetype index) -> QObject*;
353 static auto dataClear(QQmlListProperty<QObject>* property) -> void;
354 static auto childrenAppend(QQmlListProperty<QQuickItem>* property, QQuickItem* item) -> void;
355 static auto childrenCount(QQmlListProperty<QQuickItem>* property) -> qsizetype;
356 static auto childrenAt(QQmlListProperty<QQuickItem>* property, qsizetype index) -> QQuickItem*;
357 static auto childrenClear(QQmlListProperty<QQuickItem>* property) -> void;
358
359 auto axisData(Axis axis) -> AxisData&;
360 auto axisData(Axis axis) const -> const AxisData&;
361 auto axisPosition(Axis axis) const -> qreal;
362 auto setAxisPosition(Axis axis, qreal position) -> void;
363 void setContentPosition(Axis axis, qreal position);
364 auto minExtent(Axis axis) const -> qreal;
365 auto maxExtent(Axis axis) const -> qreal;
366 auto viewportSize(Axis axis) const -> qreal;
367 auto axisCanFlick(Axis axis) const -> bool;
368 auto aligned(qreal value) const -> qreal;
369 auto boundedPosition(Axis axis, qreal position) const -> qreal;
370 auto updateContentSize(Axis axis) -> void;
371 auto updateBeginningEnd() -> void;
372 auto updateVisibleArea() -> void;
373 void flushContentGeometry();
374
375 auto setAxisMoving(Axis axis, bool value) -> void;
376 auto setAxisDragging(Axis axis, bool value) -> void;
377 auto setAxisFlicking(Axis axis, bool value) -> void;
378 auto setAxisVelocity(Axis axis, qreal value) -> void;
379 auto movementStarting() -> void;
380 auto movementEnding() -> void;
381 auto flickingStarted(bool horizontal, bool vertical) -> void;
382 auto draggingStarting(bool horizontal, bool vertical) -> void;
383 auto setDragVelocity(QPointF velocity) -> void;
384 auto draggingEnding(std::optional<QPointF> releaseVelocity = std::nullopt) -> void;
385
386 auto startAxisFlick(Axis axis, qreal velocity) -> void;
387 auto stopAxisMotion(Axis axis) -> void;
388 auto requestMotionFrame() -> void;
389 auto attachMotionWindow(QQuickWindow* window) -> void;
390 void cancelScrollActivity();
391 auto advanceAxis(Axis axis, qreal now) -> void;
392 auto fixup(Axis axis) -> void;
393 auto isAxisAnimating(Axis axis) const -> bool;
394
395 auto handlePress(const QPointF& position, qint64 timestamp) -> void;
396 auto handleMove(const QPointF& position, qint64 timestamp, Qt::MouseButtons buttons) -> void;
397 auto handleRelease(const QPointF& position, qint64 timestamp) -> void;
398 auto cancelInteraction() -> void;
399 bool captureDelayedPress(QQuickItem*, QPointerEvent*);
400 void clearDelayedPress();
401 void replayDelayedPress(QPointerEvent* release = nullptr);
402 auto buttonsAccepted(const QSinglePointEvent* event) const -> bool;
403 auto pointerAccepted(QQuickItem* receiver, QEvent* event) const -> bool;
404 auto acceptsPoint(const QPointF& point) const -> bool;
405 auto eventPosition(QQuickItem* receiver, QEvent* event) const -> QPointF;
406 auto ignorePointerEvent(QEvent* event) const -> void;
407
408private:
409 QQuickItem* m_contentItem { nullptr };
410 quint64 m_geometryRevision { 0 };
411 unsigned m_geometryNotifications { 0 };
412 bool m_syncingGeometry { false };
413 QList<QObject*> m_data;
414 AxisData m_hData;
415 AxisData m_vData;
416 QPointF m_dragVelocity;
417 FlickableVisibleArea* m_visibleArea { nullptr };
418 QPointer<QQuickItem> m_interactionItem;
419 QMetaObject::Connection m_frameConnection;
420 QMetaObject::Connection m_windowVisibilityConnection;
421 bool m_motionWindowAttached = false;
422 QElapsedTimer m_motionClock;
423 QBasicTimer m_pressDelayTimer;
424 QBasicTimer m_dragVelocityTimer;
425 std::unique_ptr<QPointerEvent> m_delayedPress;
426 QPointer<QQuickItem> m_delayedReceiver;
427 QPointer<QQuickWindow> m_delayedWindow;
428 bool m_replayingPress = false;
429 QPointF m_pressPos;
430 QPointF m_lastPos;
431 qint64 m_lastPosTime { -1 };
432 qint64 m_lastPressTime { -1 };
433 FlickableDirection m_flickableDirection { AutoFlickDirection };
434 InputMaskMode m_inputMaskMode { Viewport };
435 qreal m_deceleration { 5000 };
436 qreal m_maxVelocity { 2500 };
437 int m_pressDelay { 0 };
438 bool m_pressed { false };
439 bool m_stealMouse { false };
440 bool m_interactive { true };
441 bool m_pixelAligned { false };
442 bool m_syncDrag { false };
443 Qt::MouseButtons m_acceptedButtons { Qt::LeftButton };
444};
445
446} // namespace qml_material