File indexing completed on 2024-05-05 04:47:01
0001 /* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org> 0002 * SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com> 0003 * SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 #pragma once 0007 0008 #include <QGuiApplication> 0009 #include <QObject> 0010 #include <QPoint> 0011 #include <QQuickItem> 0012 #include <QStyleHints> 0013 #include <QtQml> 0014 #include <QQmlParserStatus> 0015 #include <QPropertyAnimation> 0016 0017 class QWheelEvent; 0018 class WheelHandler; 0019 0020 /** 0021 * Describes the mouse wheel event 0022 */ 0023 class KirigamiWheelEvent : public QObject 0024 { 0025 Q_OBJECT 0026 QML_ELEMENT 0027 QML_UNCREATABLE("") 0028 /** 0029 * x: real 0030 * 0031 * X coordinate of the mouse pointer 0032 */ 0033 Q_PROPERTY(qreal x READ x CONSTANT) 0034 0035 /** 0036 * y: real 0037 * 0038 * Y coordinate of the mouse pointer 0039 */ 0040 Q_PROPERTY(qreal y READ y CONSTANT) 0041 0042 /** 0043 * angleDelta: point 0044 * 0045 * The distance the wheel is rotated in degrees. 0046 * The x and y coordinates indicate the horizontal and vertical wheels respectively. 0047 * A positive value indicates it was rotated up/right, negative, bottom/left 0048 * This value is more likely to be set in traditional mice. 0049 */ 0050 Q_PROPERTY(QPointF angleDelta READ angleDelta CONSTANT) 0051 0052 /** 0053 * pixelDelta: point 0054 * 0055 * provides the delta in screen pixels available on high resolution trackpads 0056 */ 0057 Q_PROPERTY(QPointF pixelDelta READ pixelDelta CONSTANT) 0058 0059 /** 0060 * buttons: int 0061 * 0062 * it contains an OR combination of the buttons that were pressed during the wheel, they can be: 0063 * Qt.LeftButton, Qt.MiddleButton, Qt.RightButton 0064 */ 0065 Q_PROPERTY(int buttons READ buttons CONSTANT) 0066 0067 /** 0068 * modifiers: int 0069 * 0070 * Keyboard mobifiers that were pressed during the wheel event, such as: 0071 * Qt.NoModifier (default, no modifiers) 0072 * Qt.ControlModifier 0073 * Qt.ShiftModifier 0074 * ... 0075 */ 0076 Q_PROPERTY(int modifiers READ modifiers CONSTANT) 0077 0078 /** 0079 * inverted: bool 0080 * 0081 * Whether the delta values are inverted 0082 * On some platformsthe returned delta are inverted, so positive values would mean bottom/left 0083 */ 0084 Q_PROPERTY(bool inverted READ inverted CONSTANT) 0085 0086 /** 0087 * accepted: bool 0088 * 0089 * If set, the event shouldn't be managed anymore, 0090 * for instance it can be used to block the handler to manage the scroll of a view on some scenarios 0091 * @code 0092 * // This handler handles automatically the scroll of 0093 * // flickableItem, unless Ctrl is pressed, in this case the 0094 * // app has custom code to handle Ctrl+wheel zooming 0095 * Kirigami.WheelHandler { 0096 * target: flickableItem 0097 * blockTargetWheel: true 0098 * scrollFlickableTarget: true 0099 * onWheel: { 0100 * if (wheel.modifiers & Qt.ControlModifier) { 0101 * wheel.accepted = true; 0102 * // Handle scaling of the view 0103 * } 0104 * } 0105 * } 0106 * @endcode 0107 * 0108 */ 0109 Q_PROPERTY(bool accepted READ isAccepted WRITE setAccepted) 0110 0111 public: 0112 KirigamiWheelEvent(QObject *parent = nullptr); 0113 ~KirigamiWheelEvent() override; 0114 0115 void initializeFromEvent(QWheelEvent *event); 0116 0117 qreal x() const; 0118 qreal y() const; 0119 QPointF angleDelta() const; 0120 QPointF pixelDelta() const; 0121 int buttons() const; 0122 int modifiers() const; 0123 bool inverted() const; 0124 bool isAccepted(); 0125 void setAccepted(bool accepted); 0126 0127 private: 0128 qreal m_x = 0; 0129 qreal m_y = 0; 0130 QPointF m_angleDelta; 0131 QPointF m_pixelDelta; 0132 Qt::MouseButtons m_buttons = Qt::NoButton; 0133 Qt::KeyboardModifiers m_modifiers = Qt::NoModifier; 0134 bool m_inverted = false; 0135 bool m_accepted = false; 0136 }; 0137 0138 class WheelFilterItem : public QQuickItem 0139 { 0140 Q_OBJECT 0141 public: 0142 WheelFilterItem(QQuickItem *parent = nullptr); 0143 }; 0144 0145 /** 0146 * @brief Handles scrolling for a Flickable and 2 attached ScrollBars. 0147 * 0148 * WheelHandler filters events from a Flickable, a vertical ScrollBar and a horizontal ScrollBar. 0149 * Wheel and KeyPress events (when `keyNavigationEnabled` is true) are used to scroll the Flickable. 0150 * When `filterMouseEvents` is true, WheelHandler blocks mouse button input from reaching the Flickable 0151 * and sets the `interactive` property of the scrollbars to false when touch input is used. 0152 * 0153 * Wheel event handling behavior: 0154 * 0155 * - Pixel delta is ignored unless angle delta is not available because pixel delta scrolling is too slow. Qt Widgets doesn't use pixel delta either, so the default scroll speed should be consistent with Qt Widgets. 0156 * - When using angle delta, scroll using the step increments defined by `verticalStepSize` and `horizontalStepSize`. 0157 * - When one of the keyboard modifiers in `pageScrollModifiers` is used, scroll by pages. 0158 * - When using a device that doesn't use 120 angle delta unit increments such as a touchpad, the `verticalStepSize`, `horizontalStepSize` and page increments (if using page scrolling) will be multiplied by `angle delta / 120` to keep scrolling smooth. 0159 * - If scrolling has happened in the last 400ms, use an internal QQuickItem stacked over the Flickable's contentItem to catch wheel events and use those wheel events to scroll, if possible. This prevents controls inside the Flickable's contentItem that allow scrolling to change the value (e.g., Sliders, SpinBoxes) from conflicting with scrolling the page. 0160 * 0161 * Common usage with a Flickable: 0162 * 0163 * @include wheelhandler/FlickableUsage.qml 0164 * 0165 * Common usage inside of a ScrollView template: 0166 * 0167 * @include wheelhandler/ScrollViewUsage.qml 0168 * 0169 */ 0170 class WheelHandler : public QObject, public QQmlParserStatus 0171 { 0172 Q_OBJECT 0173 Q_INTERFACES(QQmlParserStatus) 0174 QML_ELEMENT 0175 0176 /** 0177 * @brief This property holds the Qt Quick Flickable that the WheelHandler will control. 0178 */ 0179 Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged FINAL) 0180 0181 /** 0182 * @brief This property holds the vertical step size. 0183 * 0184 * The default value is equivalent to `20 * Qt.styleHints.wheelScrollLines`. This is consistent with the default increment for QScrollArea. 0185 * 0186 * @sa horizontalStepSize 0187 * 0188 * @since KDE Frameworks 5.89 0189 */ 0190 Q_PROPERTY(qreal verticalStepSize READ verticalStepSize 0191 WRITE setVerticalStepSize RESET resetVerticalStepSize 0192 NOTIFY verticalStepSizeChanged FINAL) 0193 0194 /** 0195 * @brief This property holds the horizontal step size. 0196 * 0197 * The default value is equivalent to `20 * Qt.styleHints.wheelScrollLines`. This is consistent with the default increment for QScrollArea. 0198 * 0199 * @sa verticalStepSize 0200 * 0201 * @since KDE Frameworks 5.89 0202 */ 0203 Q_PROPERTY(qreal horizontalStepSize READ horizontalStepSize 0204 WRITE setHorizontalStepSize RESET resetHorizontalStepSize 0205 NOTIFY horizontalStepSizeChanged FINAL) 0206 0207 /** 0208 * @brief This property holds the keyboard modifiers that will be used to start page scrolling. 0209 * 0210 * The default value is equivalent to `Qt.ControlModifier | Qt.ShiftModifier`. This matches QScrollBar, which uses QAbstractSlider behavior. 0211 * 0212 * @since KDE Frameworks 5.89 0213 */ 0214 Q_PROPERTY(Qt::KeyboardModifiers pageScrollModifiers READ pageScrollModifiers 0215 WRITE setPageScrollModifiers RESET resetPageScrollModifiers 0216 NOTIFY pageScrollModifiersChanged FINAL) 0217 0218 /** 0219 * @brief This property holds whether the WheelHandler filters mouse events like a Qt Quick Controls ScrollView would. 0220 * 0221 * Touch events are allowed to flick the view and they make the scrollbars not interactive. 0222 * 0223 * Mouse events are not allowed to flick the view and they make the scrollbars interactive. 0224 * 0225 * Hover events on the scrollbars and wheel events on anything also make the scrollbars interactive when this property is set to true. 0226 * 0227 * The default value is `false`. 0228 * 0229 * @since KDE Frameworks 5.89 0230 */ 0231 Q_PROPERTY(bool filterMouseEvents READ filterMouseEvents 0232 WRITE setFilterMouseEvents NOTIFY filterMouseEventsChanged FINAL) 0233 0234 /** 0235 * @brief This property holds whether the WheelHandler handles keyboard scrolling. 0236 * 0237 * - Left arrow scrolls a step to the left. 0238 * - Right arrow scrolls a step to the right. 0239 * - Up arrow scrolls a step upwards. 0240 * - Down arrow scrolls a step downwards. 0241 * - PageUp scrolls to the previous page. 0242 * - PageDown scrolls to the next page. 0243 * - Home scrolls to the beginning. 0244 * - End scrolls to the end. 0245 * - When Alt is held, scroll horizontally when using PageUp, PageDown, Home or End. 0246 * 0247 * The default value is `false`. 0248 * 0249 * @since KDE Frameworks 5.89 0250 */ 0251 Q_PROPERTY(bool keyNavigationEnabled READ keyNavigationEnabled 0252 WRITE setKeyNavigationEnabled NOTIFY keyNavigationEnabledChanged FINAL) 0253 0254 /** 0255 * @brief This property holds whether the WheelHandler blocks all wheel events from reaching the Flickable. 0256 * 0257 * When this property is false, scrolling the Flickable with WheelHandler will only block an event from reaching the Flickable if the Flickable is actually scrolled by WheelHandler. 0258 * 0259 * NOTE: Wheel events created by touchpad gestures with pixel deltas will always be accepted no matter what. This is because they will cause the Flickable to jump back to where scrolling started unless the events are always accepted before they reach the Flickable. 0260 * 0261 * The default value is true. 0262 */ 0263 Q_PROPERTY(bool blockTargetWheel MEMBER m_blockTargetWheel NOTIFY blockTargetWheelChanged) 0264 0265 /** 0266 * @brief This property holds whether the WheelHandler can use wheel events to scroll the Flickable. 0267 * 0268 * The default value is true. 0269 */ 0270 Q_PROPERTY(bool scrollFlickableTarget MEMBER m_scrollFlickableTarget NOTIFY scrollFlickableTargetChanged) 0271 0272 Q_PROPERTY(Qt::Orientation primaryOrientation READ primaryOrientation WRITE setPrimaryOrientation NOTIFY primaryOrientationChanged) 0273 0274 public: 0275 explicit WheelHandler(QObject *parent = nullptr); 0276 ~WheelHandler() override; 0277 0278 QQuickItem *target() const; 0279 void setTarget(QQuickItem *target); 0280 0281 qreal verticalStepSize() const; 0282 void setVerticalStepSize(qreal stepSize); 0283 void resetVerticalStepSize(); 0284 0285 qreal horizontalStepSize() const; 0286 void setHorizontalStepSize(qreal stepSize); 0287 void resetHorizontalStepSize(); 0288 0289 Qt::KeyboardModifiers pageScrollModifiers() const; 0290 void setPageScrollModifiers(Qt::KeyboardModifiers modifiers); 0291 void resetPageScrollModifiers(); 0292 0293 bool filterMouseEvents() const; 0294 void setFilterMouseEvents(bool enabled); 0295 0296 bool keyNavigationEnabled() const; 0297 void setKeyNavigationEnabled(bool enabled); 0298 0299 Qt::Orientation primaryOrientation() const; 0300 void setPrimaryOrientation(Qt::Orientation orientation); 0301 /** 0302 * Scroll up one step. If the stepSize parameter is less than 0, the verticalStepSize will be used. 0303 * 0304 * returns true if the contentItem was moved. 0305 * 0306 * @since KDE Frameworks 5.89 0307 */ 0308 Q_INVOKABLE bool scrollUp(qreal stepSize = -1); 0309 0310 /** 0311 * Scroll down one step. If the stepSize parameter is less than 0, the verticalStepSize will be used. 0312 * 0313 * returns true if the contentItem was moved. 0314 * 0315 * @since KDE Frameworks 5.89 0316 */ 0317 Q_INVOKABLE bool scrollDown(qreal stepSize = -1); 0318 0319 /** 0320 * Scroll left one step. If the stepSize parameter is less than 0, the horizontalStepSize will be used. 0321 * 0322 * returns true if the contentItem was moved. 0323 * 0324 * @since KDE Frameworks 5.89 0325 */ 0326 Q_INVOKABLE bool scrollLeft(qreal stepSize = -1); 0327 0328 /** 0329 * Scroll right one step. If the stepSize parameter is less than 0, the horizontalStepSize will be used. 0330 * 0331 * returns true if the contentItem was moved. 0332 * 0333 * @since KDE Frameworks 5.89 0334 */ 0335 Q_INVOKABLE bool scrollRight(qreal stepSize = -1); 0336 0337 Q_SIGNALS: 0338 void targetChanged(); 0339 void verticalStepSizeChanged(); 0340 void horizontalStepSizeChanged(); 0341 void pageScrollModifiersChanged(); 0342 void filterMouseEventsChanged(); 0343 void keyNavigationEnabledChanged(); 0344 void blockTargetWheelChanged(); 0345 void scrollFlickableTargetChanged(); 0346 void primaryOrientationChanged(); 0347 /** 0348 * @brief This signal is emitted when a wheel event reaches the event filter, just before scrolling is handled. 0349 * 0350 * Accepting the wheel event in the `onWheel` signal handler prevents scrolling from happening. 0351 */ 0352 void wheel(KirigamiWheelEvent *wheel); 0353 0354 protected: 0355 bool eventFilter(QObject *watched, QEvent *event) override; 0356 0357 private Q_SLOTS: 0358 void _k_rebindScrollBars(); 0359 0360 private: 0361 void classBegin() override; 0362 void componentComplete() override; 0363 0364 void setScrolling(bool scrolling); 0365 bool scrollFlickable(QPointF pixelDelta, 0366 QPointF angleDelta = {}, 0367 Qt::KeyboardModifiers modifiers = Qt::NoModifier); 0368 0369 QPointer<QQuickItem> m_flickable; 0370 QPointer<QQuickItem> m_verticalScrollBar; 0371 QPointer<QQuickItem> m_horizontalScrollBar; 0372 QMetaObject::Connection m_verticalChangedConnection; 0373 QMetaObject::Connection m_horizontalChangedConnection; 0374 QPointer<QQuickItem> m_filterItem; 0375 // Matches QScrollArea and QTextEdit 0376 qreal m_defaultPixelStepSize = 20 * QGuiApplication::styleHints()->wheelScrollLines(); 0377 qreal m_verticalStepSize = m_defaultPixelStepSize; 0378 qreal m_horizontalStepSize = m_defaultPixelStepSize; 0379 bool m_explicitVStepSize = false; 0380 bool m_explicitHStepSize = false; 0381 bool m_wheelScrolling = false; 0382 constexpr static qreal m_wheelScrollingDuration = 400; 0383 bool m_filterMouseEvents = false; 0384 bool m_keyNavigationEnabled = false; 0385 bool m_blockTargetWheel = true; 0386 bool m_scrollFlickableTarget = true; 0387 Qt::Orientation m_primaryOrientation = Qt::Orientation::Vertical; 0388 // Same as QXcbWindow. 0389 constexpr static Qt::KeyboardModifiers m_defaultHorizontalScrollModifiers = Qt::AltModifier; 0390 // Same as QScrollBar/QAbstractSlider. 0391 constexpr static Qt::KeyboardModifiers m_defaultPageScrollModifiers = Qt::ControlModifier | Qt::ShiftModifier; 0392 Qt::KeyboardModifiers m_pageScrollModifiers = m_defaultPageScrollModifiers; 0393 QTimer m_wheelScrollingTimer; 0394 KirigamiWheelEvent m_kirigamiWheelEvent; 0395 0396 // Smooth scrolling 0397 QQmlEngine *m_engine = nullptr; 0398 QPropertyAnimation m_yScrollAnimation{nullptr, "contentY"}; 0399 bool m_wasTouched = false; 0400 };