1 | /* |
2 | * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl> |
3 | * |
4 | * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
5 | */ |
6 | |
7 | #ifndef TOOLBARLAYOUT_H |
8 | #define TOOLBARLAYOUT_H |
9 | |
10 | #include <QQuickItem> |
11 | #include <memory> |
12 | |
13 | class ToolBarLayoutPrivate; |
14 | |
15 | /** |
16 | * Attached property for ToolBarLayout delegates. |
17 | */ |
18 | class ToolBarLayoutAttached : public QObject |
19 | { |
20 | Q_OBJECT |
21 | /** |
22 | * The action this delegate was created for. |
23 | */ |
24 | Q_PROPERTY(QObject *action READ action CONSTANT FINAL) |
25 | public: |
26 | ToolBarLayoutAttached(QObject *parent = nullptr); |
27 | |
28 | QObject *action() const; |
29 | void setAction(QObject *action); |
30 | |
31 | private: |
32 | QObject *m_action = nullptr; |
33 | }; |
34 | |
35 | /** |
36 | * An item that creates delegates for actions and lays them out in a row. |
37 | * |
38 | * This effectively combines RowLayout and Repeater in a single item, with the |
39 | * addition of some extra performance enhancing tweaks. It will create instances |
40 | * of ::fullDelegate and ::itemDelegate for each action in ::actions . These are |
41 | * then positioned horizontally. Any action that ends up being placed outside |
42 | * the width of the item is hidden and will be part of ::hiddenActions. |
43 | * |
44 | * The items created as delegates are always created asynchronously, to avoid |
45 | * creation lag spikes. Each delegate has access to the action it was created |
46 | * for through the ToolBarLayoutAttached attached property. |
47 | */ |
48 | class ToolBarLayout : public QQuickItem |
49 | { |
50 | Q_OBJECT |
51 | QML_ELEMENT |
52 | QML_ATTACHED(ToolBarLayoutAttached) |
53 | /** |
54 | * The actions this layout should create delegates for. |
55 | */ |
56 | Q_PROPERTY(QQmlListProperty<QObject> actions READ actionsProperty NOTIFY actionsChanged FINAL) |
57 | /** |
58 | * A list of actions that do not fit in the current view and are thus hidden. |
59 | */ |
60 | Q_PROPERTY(QList<QObject *> hiddenActions READ hiddenActions NOTIFY hiddenActionsChanged FINAL) |
61 | /** |
62 | * A component that is used to create full-size delegates from. |
63 | * |
64 | * Each delegate has three states, it can be full-size, icon-only or hidden. |
65 | * By default, the full-size delegate is used. When the action has the |
66 | * DisplayHint::IconOnly hint set, it will always use the iconDelegate. When |
67 | * it has the DisplayHint::KeepVisible hint set, it will use the full-size |
68 | * delegate when it fits. If not, it will use the iconDelegate, unless even |
69 | * that does not fit, in which case it will still be hidden. |
70 | */ |
71 | Q_PROPERTY(QQmlComponent *fullDelegate READ fullDelegate WRITE setFullDelegate NOTIFY fullDelegateChanged FINAL) |
72 | /** |
73 | * A component that is used to create icon-only delegates from. |
74 | * |
75 | * \sa fullDelegate |
76 | */ |
77 | Q_PROPERTY(QQmlComponent *iconDelegate READ iconDelegate WRITE setIconDelegate NOTIFY iconDelegateChanged FINAL) |
78 | /** |
79 | * A component that is used to create the "more button" item from. |
80 | * |
81 | * The more button is shown when there are actions that do not fit the |
82 | * current view. It is intended to have functionality to show these hidden |
83 | * actions, like popup a menu with them showing. |
84 | */ |
85 | Q_PROPERTY(QQmlComponent *moreButton READ moreButton WRITE setMoreButton NOTIFY moreButtonChanged FINAL) |
86 | /** |
87 | * The amount of spacing between individual delegates. |
88 | */ |
89 | Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged FINAL) |
90 | /** |
91 | * How to align the delegates within this layout. |
92 | * |
93 | * When there is more space available than required by the visible delegates, |
94 | * we need to determine how to place the delegates. This property determines |
95 | * how to do that. Note that the moreButton, if visible, will always be |
96 | * placed at the end of the layout. |
97 | */ |
98 | Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged FINAL) |
99 | /** |
100 | * The combined width of visible delegates in this layout. |
101 | */ |
102 | Q_PROPERTY(qreal visibleWidth READ visibleWidth NOTIFY visibleWidthChanged FINAL) |
103 | /** |
104 | * The minimum width this layout can have. |
105 | * |
106 | * This is equal to the width of the moreButton. |
107 | */ |
108 | Q_PROPERTY(qreal minimumWidth READ minimumWidth NOTIFY minimumWidthChanged FINAL) |
109 | /** |
110 | * Which direction to layout in. |
111 | * |
112 | * This is primarily intended to support right-to-left layouts. When set to |
113 | * LeftToRight, delegates will be layout with the first item on the left and |
114 | * following items to the right of that. The more button will be placed at |
115 | * the rightmost position. Alignment flags work normally. |
116 | * |
117 | * When set to RightToLeft, delegates will be layout with the first item on |
118 | * the right and following items to the left of that. The more button will |
119 | * be placed at the leftmost position. Alignment flags are inverted, so |
120 | * AlignLeft will align items to the right, and vice-versa. |
121 | */ |
122 | Q_PROPERTY(Qt::LayoutDirection layoutDirection READ layoutDirection WRITE setLayoutDirection NOTIFY layoutDirectionChanged FINAL) |
123 | /** |
124 | * How to handle items that do not match the toolbar's height. |
125 | * |
126 | * When toolbar items do not match the height of the toolbar, there are |
127 | * several ways we can deal with this. This property sets the preferred way. |
128 | * |
129 | * The default is HeightMode::ConstrainIfLarger . |
130 | * |
131 | * \sa HeightMode |
132 | */ |
133 | Q_PROPERTY(HeightMode heightMode READ heightMode WRITE setHeightMode NOTIFY heightModeChanged FINAL) |
134 | |
135 | public: |
136 | using ActionsProperty = QQmlListProperty<QObject>; |
137 | |
138 | /** |
139 | * An enum describing several modes that can be used to deal with items with |
140 | * a height that does not match the toolbar's height. |
141 | */ |
142 | enum HeightMode { |
143 | AlwaysCenter, ///< Always center items, allowing them to go outside the bounds of the layout if they are larger. |
144 | AlwaysFill, ///< Always match the height of the layout. Larger items will be reduced in height, smaller items will be increased. |
145 | ConstrainIfLarger, ///< If the item is larger than the toolbar, reduce its height. Otherwise center it in the toolbar. |
146 | }; |
147 | Q_ENUM(HeightMode) |
148 | |
149 | ToolBarLayout(QQuickItem *parent = nullptr); |
150 | ~ToolBarLayout() override; |
151 | |
152 | ActionsProperty actionsProperty() const; |
153 | /** |
154 | * Add an action to the list of actions. |
155 | * |
156 | * \param action The action to add. |
157 | */ |
158 | void addAction(QObject *action); |
159 | /** |
160 | * Remove an action from the list of actions. |
161 | * |
162 | * \param action The action to remove. |
163 | */ |
164 | void removeAction(QObject *action); |
165 | /** |
166 | * Clear the list of actions. |
167 | */ |
168 | void clearActions(); |
169 | Q_SIGNAL void actionsChanged(); |
170 | |
171 | QList<QObject *> hiddenActions() const; |
172 | Q_SIGNAL void hiddenActionsChanged(); |
173 | |
174 | QQmlComponent *fullDelegate() const; |
175 | void setFullDelegate(QQmlComponent *newFullDelegate); |
176 | Q_SIGNAL void fullDelegateChanged(); |
177 | |
178 | QQmlComponent *iconDelegate() const; |
179 | void setIconDelegate(QQmlComponent *newIconDelegate); |
180 | Q_SIGNAL void iconDelegateChanged(); |
181 | |
182 | QQmlComponent *moreButton() const; |
183 | void setMoreButton(QQmlComponent *newMoreButton); |
184 | Q_SIGNAL void moreButtonChanged(); |
185 | |
186 | qreal spacing() const; |
187 | void setSpacing(qreal newSpacing); |
188 | Q_SIGNAL void spacingChanged(); |
189 | |
190 | Qt::Alignment alignment() const; |
191 | void setAlignment(Qt::Alignment newAlignment); |
192 | Q_SIGNAL void alignmentChanged(); |
193 | |
194 | qreal visibleWidth() const; |
195 | Q_SIGNAL void visibleWidthChanged(); |
196 | |
197 | qreal minimumWidth() const; |
198 | Q_SIGNAL void minimumWidthChanged(); |
199 | |
200 | Qt::LayoutDirection layoutDirection() const; |
201 | void setLayoutDirection(Qt::LayoutDirection &newLayoutDirection); |
202 | Q_SIGNAL void layoutDirectionChanged(); |
203 | |
204 | HeightMode heightMode() const; |
205 | void setHeightMode(HeightMode newHeightMode); |
206 | Q_SIGNAL void heightModeChanged(); |
207 | |
208 | /** |
209 | * Queue a relayout of this layout. |
210 | * |
211 | * \note The layouting happens during the next scene graph polishing phase. |
212 | */ |
213 | Q_SLOT void relayout(); |
214 | |
215 | static ToolBarLayoutAttached *qmlAttachedProperties(QObject *object) |
216 | { |
217 | return new ToolBarLayoutAttached(object); |
218 | } |
219 | |
220 | protected: |
221 | void componentComplete() override; |
222 | void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; |
223 | void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data) override; |
224 | void updatePolish() override; |
225 | |
226 | private: |
227 | friend class ToolBarLayoutPrivate; |
228 | const std::unique_ptr<ToolBarLayoutPrivate> d; |
229 | }; |
230 | |
231 | QML_DECLARE_TYPEINFO(ToolBarLayout, QML_HAS_ATTACHED_PROPERTIES) |
232 | |
233 | #endif // TOOLBARLAYOUT_H |
234 | |