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 allowRichText: 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 | width: parent.width // Assume that we will be used as a delegate, not placed in a layout | ||||
61 | height: mainLayout.height | ||||
62 | | ||||
63 | acceptedButtons: Qt.LeftButton | Qt.RightButton | ||||
64 | hoverEnabled: true | ||||
65 | cursorShape: Qt.PointingHandCursor // To indicate that the whole thing is clickable | ||||
66 | | ||||
67 | onContainsMouseChanged: listItem.ListView.view.currentIndex = (containsMouse ? index : -1) | ||||
68 | | ||||
69 | onIsEnabledChanged: if (!listItem.isEnabled) { collapse() } | ||||
70 | | ||||
71 | onClicked: { | ||||
72 | if (!listItem.isEnabled) return | ||||
73 | | ||||
74 | // Right-click: show context menu, if defined | ||||
75 | if (contextMenu != undefined) { | ||||
76 | if (mouse.button & Qt.RightButton) { | ||||
77 | contextMenu.visualParent = parent | ||||
78 | contextMenu.prepare(); | ||||
79 | contextMenu.open(mouse.x, mouse.y) | ||||
80 | return | ||||
81 | } | ||||
82 | } | ||||
83 | | ||||
84 | // Left click: toggle expanded state | ||||
85 | if (mouse.button & Qt.LeftButton) { | ||||
86 | expandedView.visible = !expandedView.visible | ||||
87 | if (expandedView.visible) { | ||||
88 | listItem.itemExpanded(listItem) | ||||
89 | } | ||||
90 | } | ||||
91 | } | ||||
92 | | ||||
93 | ColumnLayout { | ||||
94 | id: mainLayout | ||||
95 | | ||||
96 | anchors.top: parent.top | ||||
97 | anchors.left: parent.left | ||||
98 | anchors.right: parent.right | ||||
99 | | ||||
100 | spacing: 0 | ||||
101 | | ||||
102 | RowLayout { | ||||
103 | id: mainRowLayout | ||||
104 | | ||||
105 | Layout.fillWidth: true | ||||
106 | Layout.margins: units.smallSpacing | ||||
107 | // Otherwise it becomes taller when the button appears | ||||
108 | Layout.minimumHeight: defaultActionButton.height | ||||
109 | | ||||
110 | // Icon and optional emblem | ||||
111 | PlasmaCore.IconItem { | ||||
112 | id: listItemIcon | ||||
113 | | ||||
114 | usesPlasmaTheme: listItem.iconUsesPlasmaSVG | ||||
115 | | ||||
116 | implicitWidth: units.iconSizes.medium | ||||
117 | implicitHeight: units.iconSizes.medium | ||||
118 | | ||||
119 | PlasmaCore.IconItem { | ||||
120 | id: iconEmblem | ||||
121 | | ||||
122 | visible: source != undefined && source.length > 0 | ||||
123 | | ||||
124 | anchors.right: parent.right | ||||
125 | anchors.bottom: parent.bottom | ||||
126 | | ||||
127 | implicitWidth: units.iconSizes.small | ||||
128 | implicitHeight: units.iconSizes.small | ||||
129 | } | ||||
130 | } | ||||
131 | | ||||
132 | // Title and subtitle | ||||
133 | ColumnLayout { | ||||
134 | Layout.fillWidth: true | ||||
135 | Layout.alignment: Qt.AlignVCenter | ||||
136 | | ||||
137 | spacing: 0 | ||||
138 | | ||||
139 | PlasmaExtras.Heading { | ||||
140 | id: listItemTitle | ||||
141 | | ||||
142 | Layout.fillWidth: true | ||||
143 | | ||||
144 | level: 5 | ||||
145 | | ||||
146 | textFormat: listItem.allowRichText ? Text.RichText : Text.PlainText | ||||
147 | elide: Text.ElideRight | ||||
148 | maximumLineCount: 1 | ||||
149 | | ||||
150 | // Even if it's the default item, only make it bold when | ||||
151 | // there's more than one item in the list, or else there's | ||||
152 | // only one item and it's bold, which is a little bit weird | ||||
153 | font.weight: listItem.isDefault && listItem.ListView.count > 1 | ||||
154 | ? Font.Bold | ||||
davidedmundson: Why pc2? | |||||
155 | : Font.Normal | ||||
156 | } | ||||
157 | | ||||
158 | PlasmaComponents3.Label { | ||||
159 | id: listItemSubtitle | ||||
160 | | ||||
161 | enabled: false | ||||
162 | visible: text.length > 0 | ||||
163 | | ||||
164 | Layout.fillWidth: true | ||||
165 | | ||||
166 | textFormat: listItem.allowRichText ? Text.RichText : Text.PlainText | ||||
167 | elide: Text.ElideRight | ||||
168 | maximumLineCount: subtitleCanWrap ? 9999 : 1 | ||||
169 | wrapMode: subtitleCanWrap ? Text.WordWrap : Text.NoWrap | ||||
170 | } | ||||
171 | } | ||||
172 | | ||||
173 | // Default action button | ||||
174 | PlasmaComponents3.Button { | ||||
175 | id: defaultActionButton | ||||
176 | | ||||
177 | enabled: listItem.isEnabled | ||||
178 | visible: defaultActionButtonAction | ||||
179 | && listItem.containsMouse | ||||
180 | && !busyIndicator.visible | ||||
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 | icon.width: units.iconSizes.smallMedium | ||||
183 | icon.height: units.iconSizes.smallMedium | ||||
184 | | ||||
185 | // This just adds an additional action when the button is clicked; it | ||||
186 | // also runs the default action by virtue of defaultActionButtonAction | ||||
187 | // being an alias to defaultActionButton.action | ||||
188 | onClicked: collapse() | ||||
189 | } | ||||
190 | | ||||
191 | PlasmaComponents3.BusyIndicator { | ||||
davidedmundson: Neve rdefine an implicit size from an actual size. | |||||
192 | id: busyIndicator | ||||
193 | | ||||
194 | visible: listItem.isBusy | ||||
195 | } | ||||
196 | | ||||
197 | // Expand/collapse button | ||||
198 | PlasmaComponents3.Button { | ||||
199 | visible: listItem.containsMouse | ||||
200 | | ||||
201 | // TODO: "collapse-all" and "expand-all" would be more | ||||
202 | // semantically appropriate, but they have an extra horizontal | ||||
203 | // line and don't look right here | ||||
204 | icon.name: expandedView.visible? "go-up" : "go-down" | ||||
205 | icon.width: units.iconSizes.smallMedium | ||||
206 | icon.height: units.iconSizes.smallMedium | ||||
207 | | ||||
208 | onClicked: { | ||||
209 | if (expandedView.visible) { | ||||
210 | listItem.collapse() | ||||
211 | } else { | ||||
212 | listItem.expand() | ||||
213 | } | ||||
214 | } | ||||
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 | | ||||
249 | // TODO: animate the highlight moving, as in the printers applet | ||||
250 | ListView { | ||||
251 | id: actionsList | ||||
252 | anchors.top: parent.top | ||||
253 | anchors.left: parent.left | ||||
254 | anchors.right: parent.right | ||||
255 | anchors.leftMargin: listItemIcon.width + units.smallSpacing | ||||
256 | anchors.rightMargin: listItemIcon.width + units.smallSpacing * 2 | ||||
257 | | ||||
258 | height: (units.iconSizes.smallMedium + units.smallSpacing * 2) * actionsList.count | ||||
259 | | ||||
260 | model: listItem.contextualActionsModel | ||||
261 | | ||||
262 | highlight: PlasmaComponents.Highlight {} | ||||
263 | | ||||
264 | delegate: MouseArea { | ||||
265 | id: actionItem | ||||
266 | | ||||
267 | enabled: model.enabled | ||||
268 | | ||||
269 | width: actionsList.width | ||||
270 | height: actionItemLayout.height + units.smallSpacing * 2 | ||||
271 | | ||||
272 | hoverEnabled: true | ||||
273 | | ||||
274 | onContainsMouseChanged: actionItem.ListView.view.currentIndex = (containsMouse ? index : -1) | ||||
275 | | ||||
276 | onClicked: { | ||||
277 | modelData.trigger() | ||||
278 | collapse() | ||||
279 | } | ||||
280 | | ||||
281 | RowLayout { | ||||
282 | id: actionItemLayout | ||||
283 | | ||||
284 | enabled: model.enabled | ||||
285 | | ||||
286 | anchors.left: parent.left | ||||
287 | anchors.leftMargin: units.smallSpacing | ||||
288 | anchors.right: parent.right | ||||
289 | anchors.rightMargin: units.smallSpacing | ||||
290 | anchors.verticalCenter: parent.verticalCenter | ||||
291 | | ||||
292 | PlasmaCore.IconItem { | ||||
293 | implicitWidth: units.iconSizes.smallMedium | ||||
294 | implicitHeight: units.iconSizes.smallMedium | ||||
295 | | ||||
296 | source: model.icon.name | ||||
297 | } | ||||
298 | | ||||
299 | PlasmaExtras.Heading { | ||||
300 | Layout.fillWidth: true | ||||
301 | | ||||
302 | level: 5 | ||||
303 | | ||||
304 | text: model.text | ||||
305 | textFormat: listItem.allowRichText ? Text.RichText : Text.PlainText | ||||
306 | elide: Text.ElideRight | ||||
307 | maximumLineCount: 1 | ||||
308 | } | ||||
309 | } | ||||
310 | } | ||||
311 | } | ||||
312 | } | ||||
313 | } | ||||
314 | } |
It shouldn't be in PC3. It's new API from qqc2.A