4pragma ComponentBehavior: Bound
7import QtQuick.Templates as T
10import "../core/UiMetrics.js" as UiMetrics
11import "../internal/MotionAnimation.js" as MotionAnimation
82 property bool expanded:
false
92 property Component header: null
95 property color containerColor: control.MD.Style.surfaceColor
98 property bool _modal:
false
101 property bool _syncingSelection:
false
104 property real _layoutProgress: expanded ? 1 : 0
107 readonly
property real _naturalExpandedWidth: {
108 const headerItem = headerLoader.item as Item;
109 let naturalWidth = headerItem ? headerItem.implicitWidth : 0;
110 for (let index = 0; index < count; ++index) {
111 const item = itemAt(index) as MD.NavigationRailItem;
113 naturalWidth = Math.max(naturalWidth,
114 item._expandedImplicitWidth
115 + MD.Tokens.navigationRail.itemHorizontalPadding);
122 + leftPadding + rightPadding
125 readonly
property real _expandedWidth: Math.min(
126 MD.Tokens.navigationRail.expandedContainerWidthMaximum,
127 Math.max(MD.Tokens.navigationRail.expandedContainerWidthMinimum,
128 _naturalExpandedWidth + leftPadding + rightPadding))
131 readonly
property real _itemSpacing: MD.Tokens.navigationRail.collapsedItemVerticalSpace
132 * (1 - _layoutProgress)
135 readonly property real _itemsHeight: {
137 for (let index = 0; index < count; ++index) {
138 const item = itemAt(index);
140 total += item.implicitHeight;
142 return total + Math.max(0, count - 1) * _itemSpacing;
151 readonly
property int _tabStopIndex: {
152 const selected = itemAt(currentIndex);
153 if (selected && selected.enabled)
155 for (let index = 0; index < count; ++index) {
156 const item = itemAt(index);
157 if (item && item.enabled)
164 readonly
property Item _headerItem: headerLoader.item as Item
171 function _headerFocusItem(item) {
172 const candidate = item || control._headerItem;
173 if (!candidate || !candidate.visible || !candidate.enabled)
175 if (candidate.focusPolicy !== undefined && candidate.focusPolicy !== Qt.NoFocus)
177 for (let index = 0; index < candidate.children.length; ++index) {
178 const descendant = control._headerFocusItem(candidate.children[index]);
186 readonly
property real _headerOffset: {
187 const headerItem = headerLoader.item as Item;
188 return headerItem && headerItem.height > 0
189 ? headerItem.height + MD.Tokens.navigationRail.headerSpaceMinimum
194 readonly
property real _itemsOrigin: {
196 return Math.max(headerLoader.item ? (headerLoader.item as Item).height : 0,
197 (contentArea.height - _itemsHeight) / 2);
199 return Math.max(_headerOffset, contentArea.height - _itemsHeight);
200 return _headerOffset;
208 control.expanded =
true;
215 function collapse() {
216 control.expanded =
false;
221 control.expanded = !control.expanded;
225 function _itemY(itemIndex) {
226 let itemY = control._itemsOrigin;
227 for (let index = 0; index < itemIndex; ++index) {
228 const previous = control.itemAt(index);
230 itemY += previous.implicitHeight + control._itemSpacing;
236 function _bindItems() {
237 for (let index = 0; index < control.count; ++index) {
238 const item = control.itemAt(index) as MD.NavigationRailItem;
242 item._railIndex = index;
243 item.x = Qt.binding(function() {
244 return MD.Tokens.navigationRail.itemHorizontalPadding
245 * control._layoutProgress / 2;
247 item.y = Qt.binding(function() {
return control._itemY(index); });
248 item.width = Qt.binding(function() {
249 return Math.max(0, contentArea.width
250 - MD.Tokens.navigationRail.itemHorizontalPadding
251 * control._layoutProgress);
254 control._syncSelection();
258 function _syncSelection() {
259 if (control._syncingSelection)
261 control._syncingSelection =
true;
262 for (let index = 0; index < control.count; ++index) {
263 const item = control.itemAt(index) as MD.NavigationRailItem;
265 item.checked = index === control.currentIndex;
267 control._syncingSelection =
false;
271 function _activateItem(item) {
272 if (!item || !item.enabled || item._railIndex < 0)
274 control.currentIndex = item._railIndex;
275 item.forceActiveFocus(Qt.TabFocusReason);
279 function _itemCheckedChanged(item) {
280 if (control._syncingSelection || !item || item._railIndex < 0)
283 control.currentIndex = item._railIndex;
284 else if (control.currentIndex === item._railIndex)
285 control.currentIndex = -1;
289 function _selectIndex(index) {
290 const item = control.itemAt(index) as MD.NavigationRailItem;
291 if (!item || !item.enabled)
293 control.currentIndex = index;
294 item.forceActiveFocus(Qt.TabFocusReason);
303 function _moveSelection(direction) {
304 if (control.count <= 0)
306 let index = control.currentIndex;
307 for (let attempt = 0; attempt < control.count; ++attempt) {
308 index = (index + direction + control.count) % control.count;
309 if (control._selectIndex(index))
315 function _selectBoundary(last) {
316 let index = last ? control.count - 1 : 0;
317 const end = last ? -1 : control.count;
318 const direction = last ? -1 : 1;
319 for (; index !== end; index += direction) {
320 if (control._selectIndex(index))
326 implicitWidth: _collapsedWidth + (_expandedWidth - _collapsedWidth) * _layoutProgress
327 implicitHeight: topPadding + bottomPadding + Math.max(_headerOffset + _itemsHeight,
328 MD.
Tokens.navigationRail.itemContainerHeight)
330 topPadding: SafeArea.margins.top + MD.
Tokens.navigationRail.topSpace
331 bottomPadding: SafeArea.margins.bottom + MD.
Tokens.navigationRail.topSpace
332 leftPadding: mirrored ? 0 : SafeArea.margins.left
333 rightPadding: mirrored ? SafeArea.margins.right : 0
341 focusPolicy: Qt.NoFocus
343 LayoutMirroring.childrenInherit: true
345 Accessible.role: Accessible.PageTabList
347 Behavior on _layoutProgress {
349 duration: MotionAnimation.expressiveDefaultSpatialDuration
350 easing.type: Easing.BezierSpline
351 easing.bezierCurve: MotionAnimation.expressiveDefaultSpatialCurve
355 onCountChanged: Qt.callLater(control._bindItems)
356 onCurrentIndexChanged: control._syncSelection()
357 onExpandedChanged: control._bindItems()
358 Component.onCompleted: control._bindItems()
362 Keys.onUpPressed: control._moveSelection(-1)
363 Keys.onDownPressed: control._moveSelection(1)
364 Keys.onPressed: event => {
365 if (event.key === Qt.Key_Home) {
366 control._selectBoundary(
false);
367 event.accepted =
true;
368 }
else if (event.key === Qt.Key_End) {
369 control._selectBoundary(
true);
370 event.accepted =
true;
372 event.accepted =
false;
378 objectName:
"navigationRailContent"
379 LayoutMirroring.enabled: control.mirrored
380 LayoutMirroring.childrenInherit:
true
385 objectName:
"navigationRailHeader"
387 x: control.leftPadding
388 y: control.topPadding
389 width: Math.max(0, control.width - control.leftPadding - control.rightPadding)
390 sourceComponent: control.header
391 active: control.header !== null
392 LayoutMirroring.enabled: control.mirrored
393 LayoutMirroring.childrenInherit:
true
397 Accessible.ignored:
true
400 background: MD.ElevationRectangle {
401 objectName:
"navigationRailBackground"
403 readonly
property var containerShape: control._modal && control.expanded
404 ? MD.
Tokens.navigationRail.modalContainerShape
405 : MD.Tokens.navigationRail.containerShape
407 color: control.containerColor
408 elevation: control._modal && control.expanded
409 ? MD.Tokens.navigationRail.modalContainerElevation
410 : MD.Tokens.navigationRail.containerElevation
411 topLeftRadius: UiMetrics.resolveShapeRadius(containerShape.topLeft, width, height)
412 topRightRadius: UiMetrics.resolveShapeRadius(containerShape.topRight, width, height)
413 bottomLeftRadius: UiMetrics.resolveShapeRadius(containerShape.bottomLeft, width, height)
414 bottomRightRadius: UiMetrics.resolveShapeRadius(containerShape.bottomRight, width, height)
Material Design 3 design tokens.
Tokens(QObject *parent=nullptr)
Material Design 3 Expressive navigation rail tokens.
qreal collapsedContainerWidth