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