Changeset View
Standalone View
src/declarativeimports/plasmacomponents3/ExpandableListItem.qml
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright 2020 Nate Graham <nate@kde.org> | ||||
3 | * | ||||
davidedmundson: It shouldn't be in PC3. It's new API from qqc2.A | |||||
ngraham: Where should it live? PC2? Kirigami? | |||||
Definitely can't be in PC2. Kirigami is doable, but it'd require some code changes to not use any Plasma.* imports and go through relevant abstractions Failing that plasmaextracomponents davidedmundson: Definitely can't be in PC2.
Kirigami is doable, but it'd require some code changes to not use… | |||||
4 | * This program is free software; you can redistribute it and/or modify | ||||
5 | * it under the terms of the GNU Library General Public License as | ||||
6 | * published by the Free Software Foundation; either version 2, or | ||||
7 | * (at your option) any later version. | ||||
8 | * | ||||
9 | * This program is distributed in the hope that it will be useful, | ||||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
12 | * GNU Library General Public License for more details | ||||
13 | * | ||||
14 | * You should have received a copy of the GNU Library General Public | ||||
15 | * License along with this program; if not, write to the | ||||
16 | * Free Software Foundation, Inc., | ||||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
18 | */ | ||||
19 | | ||||
20 | import QtQuick 2.6 | ||||
21 | import QtQuick.Layouts 1.1 | ||||
22 | import org.kde.plasma.core 2.0 as PlasmaCore | ||||
23 | import org.kde.plasma.plasmoid 2.0 | ||||
24 | import org.kde.plasma.components 2.0 as PlasmaComponents | ||||
davidedmundson: Unused? | |||||
ngraham: Not unused, it's needed for `Highlight`, which has no PC3 version. | |||||
I meant this one import org.kde.plasma.plasmoid 2.0 having that in library code is surprising. davidedmundson: I meant this one
import org.kde.plasma.plasmoid 2.0
having that in library code is… | |||||
25 | import org.kde.plasma.components 3.0 as PlasmaComponents3 | ||||
26 | import org.kde.plasma.extras 2.0 as PlasmaExtras | ||||
27 | | ||||
28 | MouseArea { | ||||
29 | id: listItem | ||||
30 | | ||||
31 | property alias icon: listItemIcon.source | ||||
32 | property alias iconEmblem: iconEmblem.source | ||||
33 | property alias title: listItemTitle.text | ||||
34 | property alias subtitle: listItemSubtitle.text | ||||
35 | property alias defaultActionButtonAction: defaultActionButton.action | ||||
36 | property list<QtObject> contextualActionsModel | ||||
37 | property var contextMenu | ||||
38 | property var customExpandedViewContent | ||||
39 | property bool iconUsesPlasmaSVG: false | ||||
40 | property bool isBusy: false | ||||
41 | property bool isEnabled: true | ||||
42 | property bool isDefault: false | ||||
43 | property bool allowRichText: false | ||||
44 | | ||||
45 | signal itemExpanded(variant item) | ||||
46 | | ||||
47 | function expand() { | ||||
48 | expandedView.visible = true | ||||
49 | listItem.itemExpanded(listItem) | ||||
50 | } | ||||
51 | | ||||
52 | function collapse() { | ||||
53 | expandedView.visible = false | ||||
54 | listItem.itemExpanded(null) | ||||
55 | } | ||||
56 | | ||||
57 | width: parent.width // Assume that we will be used as a delegate, not placed in a layout | ||||
58 | height: mainLayout.height | ||||
59 | | ||||
60 | acceptedButtons: Qt.LeftButton | Qt.RightButton | ||||
61 | hoverEnabled: true | ||||
62 | | ||||
63 | onContainsMouseChanged: listItem.ListView.view.currentIndex = (containsMouse ? index : -1) | ||||
64 | | ||||
65 | onIsEnabledChanged: if (!listItem.isEnabled) { collapse() } | ||||
66 | | ||||
67 | onClicked: { | ||||
68 | if (!listItem.isEnabled) return | ||||
69 | | ||||
70 | // Right-click: show context menu, if defined | ||||
71 | if (contextMenu != undefined) { | ||||
72 | if (mouse.button & Qt.RightButton) { | ||||
73 | contextMenu.visualParent = parent | ||||
74 | contextMenu.prepare(); | ||||
75 | contextMenu.open(mouse.x, mouse.y) | ||||
76 | return | ||||
77 | } | ||||
78 | } | ||||
79 | | ||||
80 | // Left click: toggle expanded state | ||||
81 | if (mouse.button & Qt.LeftButton) { | ||||
82 | expandedView.visible = !expandedView.visible | ||||
83 | if (expandedView.visible) { | ||||
84 | listItem.itemExpanded(listItem) | ||||
85 | } | ||||
86 | } | ||||
87 | } | ||||
88 | | ||||
89 | ColumnLayout { | ||||
90 | id: mainLayout | ||||
91 | | ||||
92 | anchors.top: parent.top | ||||
93 | anchors.left: parent.left | ||||
94 | anchors.right: parent.right | ||||
95 | | ||||
96 | spacing: 0 | ||||
97 | | ||||
98 | RowLayout { | ||||
99 | Layout.fillWidth: true | ||||
100 | Layout.margins: units.smallSpacing | ||||
101 | // Otherwise it becomes taller when the button appears | ||||
102 | Layout.minimumHeight: defaultActionButton.height | ||||
103 | | ||||
104 | // Icon and optional emblem | ||||
105 | PlasmaCore.IconItem { | ||||
106 | id: listItemIcon | ||||
107 | | ||||
108 | usesPlasmaTheme: listItem.iconUsesPlasmaSVG | ||||
109 | | ||||
110 | implicitWidth: units.iconSizes.medium | ||||
111 | implicitHeight: units.iconSizes.medium | ||||
112 | | ||||
113 | PlasmaCore.IconItem { | ||||
114 | id: iconEmblem | ||||
115 | | ||||
116 | visible: source != undefined && source.length > 0 | ||||
117 | | ||||
118 | anchors.right: parent.right | ||||
119 | anchors.bottom: parent.bottom | ||||
120 | | ||||
121 | implicitWidth: units.iconSizes.small | ||||
122 | implicitHeight: units.iconSizes.small | ||||
123 | } | ||||
124 | } | ||||
125 | | ||||
126 | // Title and subtitle | ||||
127 | ColumnLayout { | ||||
128 | Layout.fillWidth: true | ||||
129 | Layout.alignment: Qt.AlignVCenter | ||||
130 | | ||||
131 | spacing: 0 | ||||
132 | | ||||
133 | PlasmaExtras.Heading { | ||||
134 | id: listItemTitle | ||||
135 | | ||||
136 | Layout.fillWidth: true | ||||
137 | | ||||
138 | level: 5 | ||||
139 | | ||||
140 | textFormat: listItem.allowRichText ? Text.RichText : Text.PlainText | ||||
141 | elide: Text.ElideRight | ||||
142 | maximumLineCount: 1 | ||||
143 | | ||||
144 | // Even if it's the default item, only make it bold when | ||||
145 | // there's more than one item in the list, or else there's | ||||
146 | // only one item and it's bold, which is a little bit weird | ||||
147 | font.weight: listItem.isDefault && listItem.ListView.count > 1 | ||||
148 | ? Font.Bold | ||||
149 | : Font.Normal | ||||
150 | } | ||||
151 | | ||||
152 | PlasmaComponents.Label { | ||||
153 | id: listItemSubtitle | ||||
154 | | ||||
davidedmundson: Why pc2? | |||||
155 | enabled: false | ||||
156 | visible: text.length > 0 | ||||
157 | | ||||
158 | Layout.fillWidth: true | ||||
159 | | ||||
160 | textFormat: listItem.allowRichText ? Text.RichText : Text.PlainText | ||||
161 | elide: Text.ElideRight | ||||
162 | // No maximum line count since the subtitle could be used | ||||
163 | // to display long status messages | ||||
164 | } | ||||
165 | } | ||||
166 | | ||||
167 | // Default action button | ||||
168 | PlasmaComponents3.Button { | ||||
169 | id: defaultActionButton | ||||
170 | | ||||
171 | enabled: listItem.isEnabled | ||||
172 | visible: defaultActionButtonAction | ||||
173 | && listItem.containsMouse | ||||
174 | && !busyIndicator.visible | ||||
175 | | ||||
176 | icon.width: units.iconSizes.smallMedium | ||||
177 | icon.height: units.iconSizes.smallMedium | ||||
178 | | ||||
179 | onClicked: collapse() | ||||
180 | } | ||||
davidedmundson: Shouldn't it run the default action? | |||||
It already does by virtue of having defaultActionButtonAction being an alias to defaultActionButton.action; this just adds an additional behavior on top of that. I'll add a comment to make this clearer ngraham: It already does by virtue of having `defaultActionButtonAction` being an alias to… | |||||
181 | | ||||
182 | PlasmaComponents.BusyIndicator { | ||||
183 | id: busyIndicator | ||||
184 | | ||||
185 | visible: listItem.isBusy | ||||
186 | | ||||
187 | // Horizontally center the busy indicator inside the button | ||||
188 | // that just disappeared | ||||
189 | // TODO: or should it just be right-aligned? | ||||
190 | implicitWidth: defaultActionButton.width | ||||
191 | implicitHeight: defaultActionButton.height | ||||
davidedmundson: Neve rdefine an implicit size from an actual size. | |||||
192 | } | ||||
193 | | ||||
194 | // Expand/collapse button | ||||
195 | PlasmaComponents3.Button { | ||||
196 | visible: listItem.containsMouse | ||||
197 | | ||||
198 | // TODO: "collapse-all" and "expand-all" would be more | ||||
199 | // semantically appropriate, but they have an extra horizontal | ||||
200 | // line and don't look right here | ||||
201 | icon.name: expandedView.visible? "go-up" : "go-down" | ||||
202 | icon.width: units.iconSizes.smallMedium | ||||
203 | icon.height: units.iconSizes.smallMedium | ||||
204 | | ||||
205 | onClicked: { | ||||
206 | if (expandedView.visible) { | ||||
207 | listItem.collapse() | ||||
208 | } else { | ||||
209 | listItem.expand() | ||||
210 | } | ||||
211 | } | ||||
212 | } | ||||
213 | } | ||||
214 | | ||||
215 | | ||||
216 | // Container to hold the expanded view, whatever it is | ||||
217 | Item { | ||||
218 | id: expandedView | ||||
219 | | ||||
220 | visible: false | ||||
221 | opacity: visible ? 1.0 : 0 | ||||
222 | | ||||
223 | Layout.fillWidth: true | ||||
224 | height: customView.active | ||||
225 | ? childrenRect.height + units.smallSpacing | ||||
226 | : (actionsList.count > 0 ? childrenRect.height + units.smallSpacing : childrenRect.height) | ||||
227 | | ||||
228 | Behavior on opacity { | ||||
229 | NumberAnimation { | ||||
230 | duration: units.longDuration * 3 | ||||
231 | easing.type: Easing.InOutCubic | ||||
davidedmundson: Seems random | |||||
Not random; this is the easing curve we agreed to and is now in the HIG: https://hig.kde.org/style/animations.html#guidelines ngraham: Not random; this is the easing curve we agreed to and is now in the HIG: https://hig.kde. | |||||
davidedmundson: I meant the duration | |||||
Not sure what you mean by "random." It's a multiple of a standard duration that I chose to look good (IMO). ngraham: Not sure what you mean by "random." It's a multiple of a standard duration that I chose to look… | |||||
davidedmundson: If it's multiplied by a factor it's not a "standard duration" anymore. | |||||
to me this tells more that
mart: to me this tells more that
* default durations right now are waaay too short
* we don't take… | |||||
232 | } | ||||
233 | } | ||||
234 | | ||||
235 | // Custom view; when defined, the actions list doesn't appear | ||||
236 | Loader { | ||||
Sounds like you want 2 classes. One a subclass with the list as the custom view. Then you don't need the container, or this switching. davidedmundson: Sounds like you want 2 classes. One a subclass with the list as the custom view.
Then you… | |||||
237 | id: customView | ||||
238 | | ||||
239 | visible: status == Loader.Ready | ||||
240 | active: customExpandedViewContent != undefined | ||||
241 | sourceComponent: customExpandedViewContent | ||||
242 | | ||||
243 | anchors.top: parent.top | ||||
244 | anchors.left: parent.left | ||||
245 | anchors.right: parent.right | ||||
246 | anchors.margins: units.smallSpacing | ||||
247 | } | ||||
248 | | ||||
249 | // Default expanded view content: contextual actions list | ||||
250 | ListView { | ||||
251 | id: actionsList | ||||
252 | | ||||
253 | visible: !customView.active | ||||
254 | | ||||
255 | anchors.top: parent.top | ||||
256 | anchors.left: parent.left | ||||
257 | anchors.right: parent.right | ||||
258 | anchors.leftMargin: listItemIcon.width + units.smallSpacing * 2 | ||||
259 | anchors.rightMargin: listItemIcon.width + units.smallSpacing * 2 | ||||
260 | | ||||
261 | height: (units.iconSizes.smallMedium + units.smallSpacing * 2) * actionsList.count | ||||
262 | | ||||
263 | model: listItem.contextualActionsModel | ||||
264 | | ||||
265 | highlight: PlasmaComponents.Highlight {} | ||||
266 | | ||||
267 | delegate: MouseArea { | ||||
268 | id: actionItem | ||||
269 | | ||||
270 | enabled: model.enabled | ||||
271 | | ||||
272 | width: actionsList.width | ||||
273 | height: actionItemLayout.height + units.smallSpacing * 2 | ||||
274 | | ||||
275 | hoverEnabled: true | ||||
276 | | ||||
277 | onContainsMouseChanged: actionItem.ListView.view.currentIndex = (containsMouse ? index : -1) | ||||
278 | | ||||
279 | onClicked: { | ||||
280 | modelData.trigger() | ||||
281 | collapse() | ||||
282 | } | ||||
283 | | ||||
284 | RowLayout { | ||||
285 | id: actionItemLayout | ||||
286 | | ||||
287 | enabled: model.enabled | ||||
288 | | ||||
289 | anchors.left: parent.left | ||||
290 | anchors.leftMargin: units.smallSpacing | ||||
291 | anchors.right: parent.right | ||||
292 | anchors.rightMargin: units.smallSpacing | ||||
293 | anchors.verticalCenter: parent.verticalCenter | ||||
294 | | ||||
295 | PlasmaCore.IconItem { | ||||
296 | implicitWidth: units.iconSizes.smallMedium | ||||
297 | implicitHeight: units.iconSizes.smallMedium | ||||
298 | | ||||
299 | source: model.icon.name | ||||
300 | } | ||||
301 | | ||||
302 | PlasmaExtras.Heading { | ||||
303 | Layout.fillWidth: true | ||||
304 | | ||||
305 | level: 5 | ||||
306 | | ||||
307 | text: model.text | ||||
308 | textFormat: listItem.allowRichText ? Text.RichText : Text.PlainText | ||||
309 | elide: Text.ElideRight | ||||
310 | maximumLineCount: 1 | ||||
311 | } | ||||
312 | } | ||||
313 | } | ||||
314 | } | ||||
315 | } | ||||
316 | } | ||||
317 | } |
It shouldn't be in PC3. It's new API from qqc2.A