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.components 2.0 as PlasmaComponents // for Highlight | ||||
24 | import org.kde.plasma.components 3.0 as PlasmaComponents3 | ||||
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.extras 2.0 as PlasmaExtras | ||||
26 | | ||||
27 | MouseArea { | ||||
28 | id: listItem | ||||
29 | | ||||
30 | // Objects you feed it to define the behavior and appearance | ||||
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: actionsListComponent | ||||
39 | | ||||
40 | // Optional settings that can be overridden | ||||
41 | property bool iconUsesPlasmaSVG: false | ||||
42 | property bool isBusy: false | ||||
43 | property bool isEnabled: true | ||||
44 | property bool isDefault: false | ||||
45 | property bool allowStyledText: false | ||||
46 | property bool subtitleCanWrap: false | ||||
47 | | ||||
48 | signal itemExpanded(variant item) | ||||
49 | | ||||
50 | function expand() { | ||||
51 | expandedView.visible = true | ||||
52 | listItem.itemExpanded(listItem) | ||||
53 | } | ||||
54 | | ||||
55 | function collapse() { | ||||
56 | expandedView.visible = false | ||||
57 | listItem.itemExpanded(null) | ||||
58 | } | ||||
59 | | ||||
60 | function toggleExpanded() { | ||||
61 | expandedView.visible ? listItem.collapse() : listItem.expand() | ||||
62 | } | ||||
63 | | ||||
64 | width: parent.width // Assume that we will be used as a delegate, not placed in a layout | ||||
65 | height: mainLayout.height | ||||
66 | | ||||
67 | acceptedButtons: Qt.LeftButton | Qt.RightButton | ||||
68 | hoverEnabled: true | ||||
69 | cursorShape: Qt.PointingHandCursor // To indicate that the whole thing is clickable | ||||
70 | | ||||
71 | onContainsMouseChanged: listItem.ListView.view.currentIndex = (containsMouse ? index : -1) | ||||
72 | | ||||
73 | onIsEnabledChanged: if (!listItem.isEnabled) { collapse() } | ||||
74 | | ||||
75 | onClicked: { | ||||
76 | // Item is disabled: do nothing | ||||
77 | if (!listItem.isEnabled) return | ||||
78 | | ||||
79 | // Left click: toggle expanded state | ||||
80 | if (mouse.button & Qt.LeftButton) { | ||||
81 | listItem.toggleExpanded() | ||||
82 | } | ||||
83 | | ||||
84 | // Right-click: show context menu, if defined | ||||
85 | if (contextMenu != undefined) { | ||||
86 | if (mouse.button & Qt.RightButton) { | ||||
87 | contextMenu.visualParent = parent | ||||
88 | contextMenu.prepare(); | ||||
89 | contextMenu.open(mouse.x, mouse.y) | ||||
90 | return | ||||
91 | } | ||||
92 | } | ||||
93 | } | ||||
94 | | ||||
95 | ColumnLayout { | ||||
96 | id: mainLayout | ||||
97 | | ||||
98 | anchors.top: parent.top | ||||
99 | anchors.left: parent.left | ||||
100 | anchors.right: parent.right | ||||
101 | | ||||
102 | spacing: 0 | ||||
103 | | ||||
104 | RowLayout { | ||||
105 | id: mainRowLayout | ||||
106 | | ||||
107 | Layout.fillWidth: true | ||||
108 | Layout.margins: units.smallSpacing | ||||
109 | // Otherwise it becomes taller when the button appears | ||||
110 | Layout.minimumHeight: defaultActionButton.height | ||||
111 | | ||||
112 | // Icon and optional emblem | ||||
113 | PlasmaCore.IconItem { | ||||
114 | id: listItemIcon | ||||
115 | | ||||
116 | usesPlasmaTheme: listItem.iconUsesPlasmaSVG | ||||
117 | | ||||
118 | implicitWidth: units.iconSizes.medium | ||||
119 | implicitHeight: units.iconSizes.medium | ||||
120 | | ||||
121 | PlasmaCore.IconItem { | ||||
122 | id: iconEmblem | ||||
123 | | ||||
124 | visible: source != undefined && source.length > 0 | ||||
125 | | ||||
126 | anchors.right: parent.right | ||||
127 | anchors.bottom: parent.bottom | ||||
128 | | ||||
129 | implicitWidth: units.iconSizes.small | ||||
130 | implicitHeight: units.iconSizes.small | ||||
131 | } | ||||
132 | } | ||||
133 | | ||||
134 | // Title and subtitle | ||||
135 | ColumnLayout { | ||||
136 | Layout.fillWidth: true | ||||
137 | Layout.alignment: Qt.AlignVCenter | ||||
138 | | ||||
139 | spacing: 0 | ||||
140 | | ||||
141 | PlasmaExtras.Heading { | ||||
142 | id: listItemTitle | ||||
143 | | ||||
144 | Layout.fillWidth: true | ||||
145 | | ||||
146 | level: 5 | ||||
147 | | ||||
148 | textFormat: listItem.allowStyledText ? Text.StyledText : Text.PlainText | ||||
149 | elide: Text.ElideRight | ||||
150 | maximumLineCount: 1 | ||||
151 | | ||||
152 | // Even if it's the default item, only make it bold when | ||||
153 | // there's more than one item in the list, or else there's | ||||
154 | // only one item and it's bold, which is a little bit weird | ||||
davidedmundson: Why pc2? | |||||
155 | font.weight: listItem.isDefault && listItem.ListView.count > 1 | ||||
156 | ? Font.Bold | ||||
157 | : Font.Normal | ||||
158 | } | ||||
159 | | ||||
160 | PlasmaComponents3.Label { | ||||
161 | id: listItemSubtitle | ||||
162 | | ||||
163 | enabled: false | ||||
164 | visible: text.length > 0 | ||||
165 | | ||||
166 | Layout.fillWidth: true | ||||
167 | | ||||
168 | textFormat: listItem.allowStyledText ? Text.StyledText : Text.PlainText | ||||
169 | elide: Text.ElideRight | ||||
170 | maximumLineCount: subtitleCanWrap ? 9999 : 1 | ||||
171 | wrapMode: subtitleCanWrap ? Text.WordWrap : Text.NoWrap | ||||
172 | } | ||||
173 | } | ||||
174 | | ||||
175 | // Default action button | ||||
176 | PlasmaComponents3.Button { | ||||
177 | id: defaultActionButton | ||||
178 | | ||||
179 | enabled: listItem.isEnabled | ||||
180 | visible: defaultActionButtonAction | ||||
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 | && listItem.containsMouse | ||||
182 | && !busyIndicator.visible | ||||
183 | | ||||
184 | icon.width: units.iconSizes.smallMedium | ||||
185 | icon.height: units.iconSizes.smallMedium | ||||
186 | | ||||
187 | // This just adds an additional action when the button is clicked; it | ||||
188 | // also runs the default action by virtue of defaultActionButtonAction | ||||
189 | // being an alias to defaultActionButton.action | ||||
190 | onClicked: collapse() | ||||
191 | } | ||||
davidedmundson: Neve rdefine an implicit size from an actual size. | |||||
192 | | ||||
193 | PlasmaComponents3.BusyIndicator { | ||||
194 | id: busyIndicator | ||||
195 | | ||||
196 | visible: listItem.isBusy | ||||
197 | | ||||
198 | // Otherwise it makes the list item taller when it appears | ||||
199 | Layout.maximumHeight: defaultActionButton.implicitHeight | ||||
200 | Layout.maximumWidth: Layout.maximumHeight | ||||
201 | } | ||||
202 | | ||||
203 | // Expand/collapse button | ||||
204 | PlasmaComponents3.Button { | ||||
205 | visible: listItem.containsMouse | ||||
206 | | ||||
207 | // TODO: "collapse-all" and "expand-all" would be more | ||||
208 | // semantically appropriate, but they have an extra horizontal | ||||
209 | // line and don't look right here | ||||
210 | icon.name: expandedView.visible? "go-up" : "go-down" | ||||
211 | icon.width: units.iconSizes.smallMedium | ||||
212 | icon.height: units.iconSizes.smallMedium | ||||
213 | | ||||
214 | onClicked: listItem.toggleExpanded() | ||||
215 | } | ||||
216 | } | ||||
217 | | ||||
218 | | ||||
219 | // Expanded view, by default showing the actions list | ||||
220 | Loader { | ||||
221 | id: expandedView | ||||
222 | | ||||
223 | visible: false | ||||
224 | opacity: visible ? 1.0 : 0 | ||||
225 | | ||||
226 | active: customExpandedViewContent != undefined | ||||
227 | sourceComponent: customExpandedViewContent | ||||
228 | | ||||
229 | Layout.fillWidth: true | ||||
230 | Layout.margins: units.smallSpacing | ||||
231 | | ||||
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 | Behavior on opacity { | ||||
233 | NumberAnimation { | ||||
234 | duration: units.longDuration * 3 | ||||
235 | easing.type: Easing.InOutCubic | ||||
236 | } | ||||
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 | } | ||||
238 | } | ||||
239 | } | ||||
240 | | ||||
241 | // Default expanded view content: contextual actions list | ||||
242 | Component { | ||||
243 | id: actionsListComponent | ||||
244 | | ||||
245 | // Container for actions list, so that we can add left and right margins to it | ||||
246 | Item { | ||||
247 | height: actionsList.height | ||||
248 | width: mainRowLayout.width | ||||
249 | | ||||
250 | // TODO: Implement keyboard focus | ||||
251 | // TODO: Don't highlight the first item by default, unless it has focus | ||||
252 | // TODO: Animate the highlight moving, as in the printers applet | ||||
253 | ListView { | ||||
254 | id: actionsList | ||||
255 | | ||||
256 | anchors.top: parent.top | ||||
257 | anchors.left: parent.left | ||||
258 | anchors.right: parent.right | ||||
259 | anchors.leftMargin: listItemIcon.width + units.smallSpacing | ||||
260 | anchors.rightMargin: listItemIcon.width + units.smallSpacing * 2 | ||||
261 | | ||||
262 | height: (units.iconSizes.smallMedium + units.smallSpacing * 2) * actionsList.count | ||||
263 | | ||||
264 | focus: true | ||||
265 | clip: true | ||||
266 | | ||||
267 | model: listItem.contextualActionsModel | ||||
268 | | ||||
269 | highlight: PlasmaComponents.Highlight {} | ||||
270 | | ||||
271 | delegate: MouseArea { | ||||
272 | id: actionItem | ||||
273 | | ||||
274 | enabled: model.enabled | ||||
275 | | ||||
276 | width: actionsList.width | ||||
277 | height: actionItemLayout.height + units.smallSpacing * 2 | ||||
278 | | ||||
279 | hoverEnabled: true | ||||
280 | | ||||
281 | onContainsMouseChanged: actionItem.ListView.view.currentIndex = (containsMouse ? index : -1) | ||||
282 | | ||||
283 | onClicked: { | ||||
284 | modelData.trigger() | ||||
285 | collapse() | ||||
286 | } | ||||
287 | | ||||
288 | RowLayout { | ||||
289 | id: actionItemLayout | ||||
290 | | ||||
291 | enabled: model.enabled | ||||
292 | | ||||
293 | anchors.left: parent.left | ||||
294 | anchors.leftMargin: units.smallSpacing | ||||
295 | anchors.right: parent.right | ||||
296 | anchors.rightMargin: units.smallSpacing | ||||
297 | anchors.verticalCenter: parent.verticalCenter | ||||
298 | | ||||
299 | PlasmaCore.IconItem { | ||||
300 | implicitWidth: units.iconSizes.smallMedium | ||||
301 | implicitHeight: units.iconSizes.smallMedium | ||||
302 | | ||||
303 | source: model.icon.name | ||||
304 | } | ||||
305 | | ||||
306 | PlasmaExtras.Heading { | ||||
307 | Layout.fillWidth: true | ||||
308 | | ||||
309 | level: 5 | ||||
310 | | ||||
311 | text: model.text | ||||
312 | textFormat: listItem.allowStyledText ? Text.StyledText : Text.PlainText | ||||
313 | elide: Text.ElideRight | ||||
314 | maximumLineCount: 1 | ||||
315 | } | ||||
316 | } | ||||
317 | } | ||||
318 | } | ||||
319 | } | ||||
320 | } | ||||
321 | } |
It shouldn't be in PC3. It's new API from qqc2.A