File indexing completed on 2024-05-12 17:08:58
0001 /* 0002 SPDX-FileCopyrightText: 2021 David Edmundson <davidedmundson@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #pragma once 0008 0009 #include <optional> 0010 0011 #include <QGuiApplication> 0012 #include <QQuickItem> 0013 #include <QTimer> 0014 0015 struct InterceptedQuickItemData { 0016 QPointer<QQuickItem> item; 0017 std::optional<QPointF> interceptedHoverEnterPosition; // position of intercepted enter events 0018 0019 bool operator==(QQuickItem *otherItem) const 0020 { 0021 return otherItem == item; 0022 } 0023 0024 void operator=(QQuickItem *otherItem) 0025 { 0026 item = otherItem; 0027 interceptedHoverEnterPosition.reset(); 0028 } 0029 0030 explicit operator bool() const 0031 { 0032 return !item.isNull() && interceptedHoverEnterPosition.has_value(); 0033 } 0034 }; 0035 0036 /** 0037 * This class filters child mouse events that move from a current location towards a given edge. 0038 * 0039 * The primary use-case being where we have a list of actions that trigger on hover on one side 0040 * adjacent to a large hit area where a user will want to interact with. 0041 * 0042 * Without this filter a user moving their mouse towards the target area will trigger other hover events 0043 * 0044 * This item distinguishes mouse moves towards an edge from attempts to select another item in the combo 0045 * tree. 0046 * 0047 * See: https://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown 0048 */ 0049 class TriangleMouseFilter : public QQuickItem 0050 { 0051 Q_OBJECT 0052 0053 /** 0054 * Whether the filter is active. If false, all events will be passed through. True by default. 0055 */ 0056 Q_PROPERTY(bool active MEMBER m_active NOTIFY activeChanged) 0057 0058 /** 0059 * The timeout in ms after which the filter is disabled and the current item is selected 0060 * regardless. 0061 * 0062 * The default is 300 0063 * Setting a negative value disables the timeout 0064 */ 0065 Q_PROPERTY(int filterTimeOut MEMBER m_filterTimeout NOTIFY filterTimoutChanged) 0066 0067 /** 0068 * The edge that we want to filter mouse actions towards. 0069 * i.e if we have a listview on the left with a submenu on the right, the value 0070 * will be Qt.RightEdge 0071 * 0072 * RTL configurations must be handled explicitly by the caller 0073 */ 0074 Q_PROPERTY(Qt::Edge edge MEMBER m_edge NOTIFY edgeChanged) 0075 0076 /** 0077 * The line (as two points) representing the geometry of the edge we want to filter mouse actions towards, 0078 * relative to the filtered item. This property is a QVector of x1, y1, x2, y2 instead of a QLine 0079 * for ease of use from QML which does not have a line basic type. If edgeLine is not set or is set to 0080 * anything other than a vector of 4 ints, the edge of the applet will be used instead. 0081 */ 0082 Q_PROPERTY(QVector<int> edgeLine MEMBER m_edgeLine NOTIFY edgeLineChanged) 0083 0084 /** 0085 * Whether the filter will block the first hover enter event. False by default. 0086 */ 0087 Q_PROPERTY(bool blockFirstEnter MEMBER m_blockFirstEnter NOTIFY blockFirstEnterChanged) 0088 0089 /** 0090 * A secondary starting point (other than the interception point) to also check for mouse position. 0091 * Not considered if it has the value (0, 0). Default value (0, 0) 0092 */ 0093 Q_PROPERTY(QPointF secondaryPoint MEMBER m_secondaryPoint NOTIFY secondaryPointChanged) 0094 0095 public: 0096 TriangleMouseFilter(QQuickItem *parent = nullptr); 0097 ~TriangleMouseFilter() = default; 0098 0099 Q_SIGNALS: 0100 void filterTimoutChanged(); 0101 void edgeChanged(); 0102 void edgeLineChanged(); 0103 void activeChanged(); 0104 void blockFirstEnterChanged(); 0105 void secondaryPointChanged(); 0106 0107 protected: 0108 bool childMouseEventFilter(QQuickItem *item, QEvent *event) override; 0109 0110 private: 0111 static constexpr double VELOCITY_THRESHOLD = 0.1; 0112 static constexpr int JITTER_THRESHOLD = 1; 0113 0114 bool filterContains(const QPointF &p) const; 0115 void resendHoverEvents(const QPointF &cursorPosition); 0116 0117 QTimer m_resetTimer; 0118 0119 InterceptedQuickItemData m_interceptedHoverItem; // item newly entered but the enter event was intercepted 0120 0121 std::optional<QPointF> m_lastCursorPosition; 0122 std::optional<decltype(std::declval<QHoverEvent>().timestamp())> m_lastTimestamp; 0123 std::optional<QPointF> m_interceptionPos; // point where we started intercepting 0124 Qt::Edge m_edge = Qt::RightEdge; 0125 QVector<int> m_edgeLine; 0126 int m_filterTimeout = 300; 0127 bool m_active; 0128 bool m_blockFirstEnter; 0129 bool m_usingCustomEdgeLine; 0130 QPointF m_secondaryPoint; 0131 };