Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
NavigationRailItem.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
4pragma ComponentBehavior: Bound
5
6import QtQuick
7import QtQuick.Templates as T
8import QtQuick.Controls.impl as TImpl
9import Fluid as MD
10import "../internal" as Internal
11
37T.ItemDelegate {
38 id: control
39
44 property bool tintSourceIcon: true
45
47 property color selectedIconColor: control.MD.Style.onSecondaryContainerColor
48
50 property color selectedLabelColor: control.MD.Style.secondaryColor
51
53 property color selectedIndicatorColor: control.MD.Style.secondaryContainerColor
54
56 property color unselectedIconColor: control.MD.Style.onSurfaceVariantColor
57
59 property color unselectedLabelColor: control.MD.Style.onSurfaceVariantColor
60
62 property NavigationRail rail: null
63
65 property int _railIndex: -1
66
68 readonly property bool _railExpanded: rail ? rail.expanded : false
69
71 readonly property bool _tabStop: !rail || rail._tabStopIndex === _railIndex
72
74 readonly property real _positionProgress: rail ? rail._layoutProgress
75 : (_railExpanded ? 1 : 0)
76
78 readonly property bool _expandedPlacement: _positionProgress >= 0.5
79
81 readonly property bool _hasNamedIcon: icon.name.length > 0
82
84 readonly property bool _hasSourceIcon: icon.source.toString().length > 0
85
87 readonly property bool _hasLabel: text.length > 0
88
90 readonly property color _iconColor: checked ? selectedIconColor : unselectedIconColor
91
93 readonly property color _labelColor: checked ? selectedLabelColor : unselectedLabelColor
94
96 readonly property real _contentOpacity: enabled ? 1 : MD.Tokens.navigationRail.disabledContentOpacity
97
99 readonly property real _stateLayerOpacity: {
100 if (!enabled)
101 return 0;
102 if (down)
103 return MD.Tokens.navigationRail.pressedStateLayerOpacity;
104 if (visualFocus)
105 return MD.Tokens.navigationRail.focusStateLayerOpacity;
106 if (hovered)
107 return MD.Tokens.navigationRail.hoverStateLayerOpacity;
108 return 0;
109 }
110
111 checkable: true
112 autoExclusive: true
113 hoverEnabled: true
114 focusPolicy: _tabStop || activeFocus ? Qt.StrongFocus : Qt.ClickFocus
115 padding: 0
116 spacing: _railExpanded ? MD.Tokens.navigationRail.horizontalIconLabelSpace
117 : MD.Tokens.navigationRail.verticalIconLabelSpace
118
119 icon.width: MD.Tokens.navigationRail.iconSize
120 icon.height: MD.Tokens.navigationRail.iconSize
121
122 Accessible.role: Accessible.PageTab
123 Accessible.name: text
124 Accessible.selectable: true
125 Accessible.selected: checked
126
128 readonly property real _expandedImplicitWidth:
129 MD.Tokens.navigationRail.horizontalFullWidthLeadingSpace
130 + icon.width
131 + (_hasLabel
132 ? MD.Tokens.navigationRail.horizontalIconLabelSpace
133 + expandedLabelMeasure.implicitWidth
134 : 0)
135 + MD.Tokens.navigationRail.horizontalFullWidthTrailingSpace
136
137 implicitWidth: _railExpanded
138 ? _expandedImplicitWidth
139 : MD.Tokens.navigationRail.collapsedContainerWidth
140 implicitHeight: MD.Tokens.navigationRail.itemContainerHeight
141 + (MD.Tokens.navigationRail.horizontalActiveIndicatorHeight
142 - MD.Tokens.navigationRail.itemContainerHeight) * _positionProgress
143
144 Keys.onUpPressed: event => {
145 if (control.rail)
146 control.rail._moveSelection(-1);
147 else
148 event.accepted = false;
149 }
150 Keys.onDownPressed: event => {
151 if (control.rail)
152 control.rail._moveSelection(1);
153 else
154 event.accepted = false;
155 }
156 Keys.onPressed: event => {
157 if (!control.rail) {
158 event.accepted = false;
159 } else if (event.key === Qt.Key_Home) {
160 control.rail._selectBoundary(false);
161 event.accepted = true;
162 } else if (event.key === Qt.Key_End) {
163 control.rail._selectBoundary(true);
164 event.accepted = true;
165 } else {
166 event.accepted = false;
167 }
168 }
169
170 Connections {
171 target: control
172
173 function onClicked() {
174 if (control.rail)
175 control.rail._activateItem(control);
176 }
177
178 function onCheckedChanged() {
179 if (control.rail)
180 control.rail._itemCheckedChanged(control);
181 }
182 }
183
184 contentItem: Item {
185 id: content
186 objectName: "navigationRailItemContent"
187
188 function lerp(start, end, progress) {
189 return start + (end - start) * progress;
190 }
191
192 readonly property real collapsedIndicatorX: (width - MD.Tokens.navigationRail.verticalActiveIndicatorWidth) / 2
193 readonly property real collapsedIndicatorY: control._hasLabel
194 ? 0
195 : (height - MD.Tokens.navigationRail.verticalActiveIndicatorHeight) / 2
196 readonly property real expandedIndicatorY: (height - MD.Tokens.navigationRail.horizontalActiveIndicatorHeight) / 2
197 readonly property real collapsedIconX: (width - control.icon.width) / 2
198 readonly property real collapsedIconY: collapsedIndicatorY
199 + (MD.Tokens.navigationRail.verticalActiveIndicatorHeight - control.icon.height) / 2
200 readonly property real expandedIconX: control._hasLabel
201 ? control.mirrored
202 ? width - MD.Tokens.navigationRail.horizontalFullWidthLeadingSpace - control.icon.width
203 : MD.Tokens.navigationRail.horizontalFullWidthLeadingSpace
204 : (width - control.icon.width) / 2
205 readonly property real expandedIconY: (height - control.icon.height) / 2
206 readonly property real collapsedLabelWidth: Math.min(width,
207 collapsedLabelMeasure.implicitWidth)
208
209 implicitWidth: control.implicitWidth
210 implicitHeight: control.implicitHeight
211
212 MD.Label {
213 id: collapsedLabelMeasure
214 visible: false
215 text: control.text
216 typescale: MD.Tokens.typescale.labelMedium
217 wrapMode: Text.NoWrap
218 }
219
220 MD.Label {
221 id: expandedLabelMeasure
222 visible: false
223 text: control.text
224 typescale: MD.Tokens.typescale.labelLarge
225 wrapMode: Text.NoWrap
226 }
227
228 Internal.NavigationRailItemIndicator {
229 id: indicator
230 objectName: "navigationRailItemIndicator"
231
232 x: content.lerp(content.collapsedIndicatorX, 0, control._positionProgress)
233 y: content.lerp(content.collapsedIndicatorY, content.expandedIndicatorY,
234 control._positionProgress)
235 width: content.lerp(MD.Tokens.navigationRail.verticalActiveIndicatorWidth,
236 content.width, control._positionProgress)
237 height: content.lerp(MD.Tokens.navigationRail.verticalActiveIndicatorHeight,
238 MD.Tokens.navigationRail.horizontalActiveIndicatorHeight,
239 control._positionProgress)
240 selected: control.checked
241 pressed: control.down
242 pressX: control.pressX - x
243 pressY: control.pressY - y
244 selectedColor: control.selectedIndicatorColor
245 stateLayerColor: control._iconColor
246 stateLayerOpacity: control._stateLayerOpacity
247
248 }
249
250 Item {
251 id: iconSlot
252 objectName: "navigationRailItemIcon"
253
254 x: content.lerp(content.collapsedIconX, content.expandedIconX,
255 control._positionProgress)
256 y: content.lerp(content.collapsedIconY, content.expandedIconY,
257 control._positionProgress)
258 width: control.icon.width
259 height: control.icon.height
260 opacity: control._contentOpacity
261
262 TImpl.IconImage {
263 id: sourceIcon
264 anchors.fill: iconSlot
265 source: control.icon.source
266 sourceSize: Qt.size(sourceIcon.width, sourceIcon.height)
267 fillMode: Image.PreserveAspectFit
268 color: control.tintSourceIcon ? control._iconColor : "transparent"
269 visible: control._hasSourceIcon
270 }
271
272 MD.Symbol {
273 anchors.fill: parent
274 name: control.icon.name
275 iconWidth: width
276 iconHeight: height
277 color: control._iconColor
278 fill: control.checked
279 visible: !control._hasSourceIcon && control._hasNamedIcon
280 }
281 }
282
283 MD.Label {
284 id: label
285 objectName: "navigationRailItemLabel"
286
287 x: control._expandedPlacement
288 ? control.mirrored
289 ? MD.Tokens.navigationRail.horizontalFullWidthTrailingSpace
290 : MD.Tokens.navigationRail.horizontalFullWidthLeadingSpace
291 + control.icon.width + MD.Tokens.navigationRail.horizontalIconLabelSpace
292 : (content.width - content.collapsedLabelWidth) / 2
293 y: control._expandedPlacement
294 ? (content.height - height) / 2
295 : content.collapsedIndicatorY
296 + MD.Tokens.navigationRail.verticalActiveIndicatorHeight
297 + MD.Tokens.navigationRail.verticalIconLabelSpace
298 width: control._expandedPlacement
299 ? control.mirrored
300 ? Math.max(0, content.expandedIconX
301 - MD.Tokens.navigationRail.horizontalIconLabelSpace
302 - MD.Tokens.navigationRail.horizontalFullWidthTrailingSpace)
303 : Math.max(0, content.width
304 - MD.Tokens.navigationRail.horizontalFullWidthLeadingSpace
305 - control.icon.width
306 - MD.Tokens.navigationRail.horizontalIconLabelSpace
307 - MD.Tokens.navigationRail.horizontalFullWidthTrailingSpace)
308 : content.collapsedLabelWidth
309 text: control.text
310 color: control._labelColor
311 opacity: control._contentOpacity
312 * Math.pow(control._positionProgress * 2 - 1, 2)
313 visible: control._hasLabel
314 elide: Text.ElideRight
315 wrapMode: Text.NoWrap
316 typescale: control._expandedPlacement ? MD.Tokens.typescale.labelLarge
317 : MD.Tokens.typescale.labelMedium
318 horizontalAlignment: control._expandedPlacement
319 ? (control.mirrored ? Text.AlignRight : Text.AlignLeft)
320 : Text.AlignHCenter
321 Accessible.ignored: true
322
323 }
324 }
325
326 background: Item {}
327}
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
Material Design 3 Expressive navigation rail tokens.
Material Symbols icon tokens.
Definition symbol.h:17