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