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