Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
ListItem.qml
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// SPDX-License-Identifier: MPL-2.0
3
4import QtQuick
5import QtQuick.Layouts
6import QtQml.Models
7import QtQuick.Templates as T
8import Fluid as MD
9import "../internal/MotionAnimation.js" as MotionAnimation
10import "../core/UiMetrics.js" as UiMetrics
11
43T.ItemDelegate {
44 id: control
45
51 property string overline: ""
52
59 property string supportingText: ""
60
67 property Component leading: null
68
75 property Component trailing: null
76
86 property bool segmented: false
87
100 readonly property bool _first: ListView.view !== null && DelegateModel.itemsIndex === 0
101
114 readonly property bool _last: ListView.view !== null && DelegateModel.itemsIndex === ListView.view.count - 1
115
116 QtObject {
117 id: internal
118
119 // Active text row count: label (always 1) plus optional overline and
120 // optional supporting text. Determines the item's baseline height.
121 readonly property int lineCount: 1 + (control.overline.length > 0 ? 1 : 0) + (control.supportingText.length > 0 ? 1 : 0)
122 }
123
124 implicitWidth: Math.max(parent ? parent.width : 0, implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding)
125 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding)
126
127 topPadding: MD.Tokens.listItem.topSpace
128 bottomPadding: MD.Tokens.listItem.bottomSpace
129 leftPadding: MD.Tokens.listItem.leadingSpace
130 rightPadding: MD.Tokens.listItem.trailingSpace
131 spacing: MD.Tokens.listItem.betweenSpace
132
133 topInset: control.segmented && !control._first ? MD.Tokens.listItem.segmentedGap : 0
134 bottomInset: control.segmented && !control._last ? MD.Tokens.listItem.segmentedGap : 0
135
136 hoverEnabled: true
137 focusPolicy: Qt.StrongFocus
138 Accessible.name: text
139 Accessible.description: supportingText
140
141 // -------------------------------------------------------------------------
142 // State object — holds all interaction-driven visual values in one place
143 // so the states array only needs PropertyChanges targets, not inline logic.
144 // -------------------------------------------------------------------------
145 QtObject {
146 id: _state
147
148 property color containerColor: control.segmented ? control.MD.Style.surfaceContainerColor : control.MD.Style.surfaceColor
149 property color labelColor: control.MD.Style.onSurfaceColor
150 property color overlineColor: control.MD.Style.onSurfaceVariantColor
151 property color supportingTextColor: control.MD.Style.onSurfaceVariantColor
152 property color stateLayerColor: "transparent"
153 property real stateLayerOpacity: 0.0
154 property real contentOpacity: 1.0
155 }
156
157 states: [
158 State {
159 name: "disabled"
160 when: !control.enabled
161
162 PropertyChanges {
163 _state {
164 labelColor: control.MD.Style.onSurfaceColor
165 overlineColor: control.MD.Style.onSurfaceColor
166 supportingTextColor: control.MD.Style.onSurfaceColor
167 contentOpacity: MD.Tokens.listItem.disabledContentOpacity
168 }
169 }
170 },
171 State {
172 name: "hovered"
173 when: control.hovered && control.enabled
174
175 PropertyChanges {
176 _state {
177 stateLayerColor: control.MD.Style.onSurfaceColor
178 stateLayerOpacity: MD.Tokens.listItem.hoverStateLayerOpacity
179 }
180 }
181 },
182 State {
183 name: "focused"
184 when: control.visualFocus && control.enabled
185
186 PropertyChanges {
187 _state {
188 stateLayerColor: control.MD.Style.onSurfaceColor
189 stateLayerOpacity: MD.Tokens.listItem.focusStateLayerOpacity
190 }
191 }
192 },
193 State {
194 name: "pressed"
195 when: control.down && control.enabled
196
197 PropertyChanges {
198 _state {
199 stateLayerColor: control.MD.Style.onSurfaceColor
200 stateLayerOpacity: MD.Tokens.listItem.pressedStateLayerOpacity
201 }
202 }
203 }
204 ]
205
206 // -------------------------------------------------------------------------
207 // Content item: a RowLayout that arranges the three slots horizontally.
208 //
209 // The leading and trailing Loaders live as siblings to the RowLayout inside
210 // this Item. LayoutItemProxy nodes inside the RowLayout reference those
211 // loaded items, forwarding their implicit sizes and any Layout-attached
212 // properties to the row, and positioning the real items inside the slot.
213 // -------------------------------------------------------------------------
214 contentItem: Item {
215 implicitWidth: _row.implicitWidth
216 implicitHeight: _row.implicitHeight
217
218 // Loaders are declared outside the RowLayout so that LayoutItemProxy
219 // can position the instantiated items within the layout geometry while
220 // the items remain direct children of this contentItem.
221 Loader {
222 id: _leadingLoader
223
224 sourceComponent: control.leading
225 active: control.leading !== null
226 opacity: _state.contentOpacity
227 }
228
229 Loader {
230 id: _trailingLoader
231
232 sourceComponent: control.trailing
233 active: control.trailing !== null
234 opacity: _state.contentOpacity
235 }
236
237 RowLayout {
238 id: _row
239
240 anchors.fill: parent
241 spacing: control.spacing
242
243 // Leading slot — LayoutItemProxy positions the loaded item here and
244 // forwards any Layout-attached properties it declares.
245 LayoutItemProxy {
246 target: _leadingLoader.item as Item
247 visible: _leadingLoader.item !== null
248
249 // MD3: for three-line items the leading element aligns to the
250 // top with a small top offset; otherwise it centres vertically.
251 Layout.alignment: internal.lineCount >= 3 ? (Qt.AlignTop | Qt.AlignLeft) : Qt.AlignVCenter
252 Layout.topMargin: internal.lineCount >= 3 ? 4 : 0
253 }
254
255 // Content column: overline → label → supporting text
256 ColumnLayout {
257 Layout.fillWidth: true
258 Layout.alignment: Qt.AlignVCenter
259 spacing: 0
260
261 // Overline — optional, labelSmall typescale
262 MD.Label {
263 Layout.fillWidth: true
264
265 typescale: MD.Tokens.typescale.labelSmall
266 text: control.overline
267 color: _state.overlineColor
268 opacity: _state.contentOpacity
269 // font.pixelSize: MD.Tokens.typescale.labelSmall.fontSize
270 // font.weight: MD.Tokens.typescale.labelSmall.fontWeight
271 // font.letterSpacing: MD.Tokens.typescale.labelSmall.tracking
272 elide: Text.ElideRight
273 visible: control.overline.length > 0
274 Accessible.ignored: true
275 }
276
277 // Label — required, bodyLarge typescale
278 MD.Label {
279 Layout.fillWidth: true
280
281 typescale: MD.Tokens.typescale.bodyLarge
282 text: control.text
283 Accessible.ignored: true
284 color: _state.labelColor
285 opacity: _state.contentOpacity
286 // font.pixelSize: MD.Tokens.typescale.bodyLarge.fontSize
287 // font.weight: MD.Tokens.typescale.bodyLarge.fontWeight
288 // font.letterSpacing: MD.Tokens.typescale.bodyLarge.tracking
289 elide: Text.ElideRight
290 }
291
292 // Supporting text — optional, bodyMedium typescale, ≤ 2 lines
293 MD.Label {
294 Layout.fillWidth: true
295
296 typescale: MD.Tokens.typescale.bodyMedium
297 text: control.supportingText
298 color: _state.supportingTextColor
299 opacity: _state.contentOpacity
300 // font.pixelSize: MD.Tokens.typescale.bodyMedium.fontSize
301 // font.weight: MD.Tokens.typescale.bodyMedium.fontWeight
302 // font.letterSpacing: MD.Tokens.typescale.bodyMedium.tracking
303 maximumLineCount: 2
304 wrapMode: Text.WordWrap
305 elide: Text.ElideRight
306 visible: control.supportingText.length > 0
307 Accessible.ignored: true
308 }
309 }
310
311 // Trailing slot — LayoutItemProxy positions the loaded item here and
312 // forwards any Layout-attached properties it declares.
313 LayoutItemProxy {
314 target: _trailingLoader.item as Item
315 visible: _trailingLoader.item !== null
316 Layout.alignment: internal.lineCount >= 3 ? (Qt.AlignTop | Qt.AlignRight) : Qt.AlignVCenter
317 Layout.topMargin: internal.lineCount >= 3 ? 4 : 0
318 }
319 }
320 }
321
322 // -------------------------------------------------------------------------
323 // Background: surface container with an MD3 ripple state layer.
324 // implicitHeight encodes the MD3 baseline heights: 56 / 72 / 88 dp.
325 // -------------------------------------------------------------------------
326 background: Rectangle {
327 implicitWidth: 64
328 implicitHeight: {
329 if (internal.lineCount >= 3)
330 return MD.Tokens.listItem.threeLineContainerHeight;
331 if (internal.lineCount === 2)
332 return MD.Tokens.listItem.twoLineContainerHeight;
333 return MD.Tokens.listItem.oneLineContainerHeight;
334 }
335
336 topLeftRadius: control.segmented && control._first
337 ? UiMetrics.resolveShapeRadius(
338 MD.Tokens.listItem.selectedContainerExpressiveShape.topLeft,
339 width, height) : 0
340 topRightRadius: control.segmented && control._first
341 ? UiMetrics.resolveShapeRadius(
342 MD.Tokens.listItem.selectedContainerExpressiveShape.topRight,
343 width, height) : 0
344 bottomLeftRadius: control.segmented && control._last
345 ? UiMetrics.resolveShapeRadius(
346 MD.Tokens.listItem.selectedContainerExpressiveShape.bottomLeft,
347 width, height) : 0
348 bottomRightRadius: control.segmented && control._last
349 ? UiMetrics.resolveShapeRadius(
350 MD.Tokens.listItem.selectedContainerExpressiveShape.bottomRight,
351 width, height) : 0
352
353 color: _state.containerColor
354
355 Behavior on topLeftRadius {
356 NumberAnimation {
357 easing.type: Easing.BezierSpline
358 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
359 duration: MotionAnimation.expressiveFastSpatialDuration
360 }
361 }
362 Behavior on topRightRadius {
363 NumberAnimation {
364 easing.type: Easing.BezierSpline
365 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
366 duration: MotionAnimation.expressiveFastSpatialDuration
367 }
368 }
369 Behavior on bottomLeftRadius {
370 NumberAnimation {
371 easing.type: Easing.BezierSpline
372 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
373 duration: MotionAnimation.expressiveFastSpatialDuration
374 }
375 }
376 Behavior on bottomRightRadius {
377 NumberAnimation {
378 easing.type: Easing.BezierSpline
379 easing.bezierCurve: MotionAnimation.expressiveFastSpatialCurve
380 duration: MotionAnimation.expressiveFastSpatialDuration
381 }
382 }
383
384 Behavior on color {
385 ColorAnimation {
386 duration: MD.Tokens.motion.duration.short2
387 }
388 }
389
390 MD.Ripple {
391 anchors.fill: parent
392 topLeftRadius: parent.topLeftRadius
393 topRightRadius: parent.topRightRadius
394 bottomLeftRadius: parent.bottomLeftRadius
395 bottomRightRadius: parent.bottomRightRadius
396 color: _state.stateLayerColor
397 pressed: control.down
398 pressX: control.pressX
399 pressY: control.pressY
400 stateOpacity: _state.stateLayerOpacity
401 }
402 }
403}
Material Design 3 design tokens.
Definition tokens.h:71
Tokens(QObject *parent=nullptr)
Definition tokens.cpp:8
Displays text using a selectable Material 3 type scale.
Definition Label.qml:15