4pragma ComponentBehavior: Bound
7import QtQuick.Templates as T
9import "../internal" as Internal
153 enum Variant { Primary, Secondary }
154 enum LayoutMode { Fixed, Scrollable }
156 property int variant: MD.TabBar.Primary
158 property int layoutMode: MD.TabBar.Fixed
160 property color containerColor: control.MD.Style.surfaceColor
162 property color indicatorColor: control.MD.Style.primaryColor
164 property color dividerColor: control.MD.Style.outlineVariantColor
166 property int _focusIndex: -1
167 property bool _initialized:
false
168 property bool _destroying:
false
169 readonly
property bool _scrollable: layoutMode === MD.TabBar.Scrollable
170 readonly
property int _visibleCount: {
172 for (let i = 0; i < count; ++i) {
173 const item = itemAt(i);
174 if (item && item.visible)
179 readonly
property real _naturalWidth: {
181 for (let i = 0; i < count; ++i) {
182 const item = itemAt(i);
183 if (item && item.visible)
184 result += item.implicitWidth;
186 return result + (layoutMode === MD.TabBar.Scrollable && _visibleCount > 0
187 ? MD.Tokens.tab.scrollableLeadingOffset : 0);
189 readonly
property real _naturalHeight: {
190 let result = variant === MD.TabBar.Primary ? MD.Tokens.tab.primaryContainerHeight
191 : MD.Tokens.tab.secondaryContainerHeight;
192 for (let i = 0; i < count; ++i) {
193 const item = itemAt(i);
194 if (item && item.visible)
195 result = Math.max(result, item.implicitHeight);
199 readonly
property real _minimumTabWidth: {
200 let result = MD.Tokens.tab.minimumInteractiveSize;
201 for (let i = 0; i < count; ++i) {
202 const item = itemAt(i) as MD.TabButton;
203 if (item && item.visible)
204 result = Math.max(result, item._minimumWidth);
209 implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
210 _naturalWidth + leftPadding + rightPadding)
211 implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
212 _naturalHeight + topPadding + bottomPadding)
215 focusPolicy: Qt.NoFocus
217 LayoutMirroring.childrenInherit:
true
219 function _indexOf(item) {
220 for (let i = 0; i < count; ++i) {
221 if (itemAt(i) === item)
226 function _available(index) {
227 const item = index >= 0 && index < count ? itemAt(index) : null;
228 return item !== null && item.visible && item.enabled;
230 function _entryIndex() {
231 if (_available(currentIndex))
233 for (let i = 0; i < count; ++i) {
239 function _isTabStop(item) {
240 const index = _available(_focusIndex) ? _focusIndex : _entryIndex();
241 return _indexOf(item) === index;
243 function _widthFor(item) {
244 if (control._scrollable)
245 return item.implicitWidth;
246 return _visibleCount > 0 ? Math.max(_minimumTabWidth,
247 availableWidth / _visibleCount) : 0;
249 function _focusEntered(item) {
250 _focusIndex = _indexOf(item);
253 function _focusedItem() {
254 for (let i = 0; i < count; ++i) {
255 const item = itemAt(i);
256 if (item && item.activeFocus)
261 function _focusLeft() {
262 if (control._destroying)
265 _focusIndex = _entryIndex();
267 function _focusAt(index) {
268 if (!_available(index))
271 const item = itemAt(index);
272 item.forceActiveFocus(Qt.TabFocusReason);
275 function _handleNavigation(event) {
277 if (event.key === Qt.Key_Left)
278 delta = mirrored ? 1 : -1;
279 else if (event.key === Qt.Key_Right)
280 delta = mirrored ? -1 : 1;
281 else if (event.key === Qt.Key_Home || event.key === Qt.Key_End) {
282 const fromEnd =
event.key === Qt.Key_End;
283 for (let step = 0; step < count; ++step) {
284 const index = fromEnd ? count - 1 - step : step;
285 if (_available(index)) {
290 event.accepted =
true;
295 const focused = _focusedItem();
296 const start = focused ? _indexOf(focused) : _entryIndex();
297 for (let step = 1; step <= count; ++step) {
298 const index = ((start + delta * step) % count + count) % count;
299 if (_available(index)) {
304 event.accepted =
true;
307 function _availabilityChanged() {
308 if (control._destroying)
310 const focused = _focusedItem();
311 if (focused && !_available(_indexOf(focused)))
312 _focusAt(_entryIndex());
314 _focusIndex = _entryIndex();
317 function _reveal(item) {
318 if (!item || !item.visible || viewport.width <= 0)
320 let bounds = item.mapToItem(viewport, 0, 0);
321 const realItem = viewport.itemAt(item.x + item.width / 2, item.y + item.height / 2) === item;
322 const contained = item.width <= viewport.width
323 ? bounds.x >= 0 && bounds.x + item.width <= viewport.width
324 : bounds.x <= 0 && bounds.x + item.width >= viewport.width;
325 if (realItem && contained)
327 const index = control._indexOf(item);
328 if ((!realItem || !contained) && index >= 0)
329 viewport.positionViewAtIndex(index, ListView.Contain);
330 bounds = item.mapToItem(viewport, 0, 0);
331 let destination = viewport.contentX;
333 destination += bounds.x;
334 else if (bounds.x + item.width > viewport.width)
335 destination += bounds.x + item.width - viewport.width;
336 viewport.contentX = Math.max(viewport._minimumContentX,
337 Math.min(viewport._maximumContentX, destination));
339 function _revealCurrent() {
340 const focused = _focusedItem();
341 _reveal(focused || currentItem);
343 function _revealSelected() {
344 _reveal(currentItem);
346 function _scheduleSelectedReveal() {
347 if (!control._destroying)
348 Qt.callLater(control._revealSelected);
350 function _scheduleReveal() {
351 if (!control._destroying)
352 Qt.callLater(control._revealCurrent);
355 onCountChanged: Qt.callLater(control._availabilityChanged)
356 onCurrentIndexChanged: {
358 _focusIndex = _entryIndex();
359 _scheduleSelectedReveal();
361 onCurrentItemChanged: _scheduleSelectedReveal()
362 onAvailableWidthChanged: _scheduleReveal()
363 onLayoutModeChanged: _scheduleReveal()
364 onMirroredChanged: _scheduleReveal()
365 Component.onCompleted: {
366 _focusIndex = _entryIndex();
367 Qt.callLater(function() { control._initialized =
true; control._revealCurrent(); });
369 Component.onDestruction: control._destroying =
true
371 Keys.priority: Keys.BeforeItem
372 Keys.onPressed:
event => { control._handleNavigation(event); }
374 contentItem: ListView {
376 objectName:
"tabViewport"
377 model: control.contentModel
378 orientation: ListView.Horizontal
380 interactive: contentWidth > width
382 boundsBehavior: Flickable.StopAtBounds
383 boundsMovement: Flickable.StopAtBounds
384 flickableDirection: Flickable.HorizontalFlick
385 snapMode: ListView.NoSnap
386 readonly
property real _contentStart: {
387 let result = headerItem ? headerItem.x : 0;
388 for (let i = 0; i < control.count; ++i) {
389 const item = control.itemAt(i);
390 if (item && item.visible)
391 result = Math.min(result, item.x);
395 readonly
property real _contentEnd: {
396 let result = headerItem ? headerItem.x + headerItem.width : 0;
397 for (let i = 0; i < control.count; ++i) {
398 const item = control.itemAt(i);
399 if (item && item.visible)
400 result = Math.max(result, item.x + item.width);
404 readonly
property real _minimumContentX: control.mirrored
405 ? Math.min(_contentStart, _contentEnd - width) : _contentStart
406 readonly
property real _maximumContentX: control.mirrored
407 ? _contentEnd - width : Math.max(_contentStart, _contentEnd - width)
409 width: control.layoutMode === MD.TabBar.Scrollable && control._visibleCount > 0
410 ? MD.Tokens.tab.scrollableLeadingOffset : 0
411 height: viewport.height
414 Internal.TabIndicator {
416 primary: control.variant === MD.TabBar.Primary
417 readonly
property MD.TabButton selectedTab: control.currentItem as MD.TabButton
418 readonly
property real selectedWidth: selectedTab && selectedTab.visible
419 ? (primary ? selectedTab._primaryIndicatorWidth : selectedTab.width) : 0
420 targetWidth: selectedWidth
421 targetX: selectedTab ? selectedTab.x - viewport.contentX
422 + (selectedTab.width - selectedWidth) / 2 : 0
423 y: viewport.height - height
424 indicatorColor: control.indicatorColor
425 animationEnabled: control._initialized && !viewport.moving
426 visible: selectedWidth > 0
431 background: Rectangle {
432 color: control.containerColor
434 objectName:
"tabDivider"
437 width: viewport.width
438 height: MD.
Tokens.tab.dividerHeight
439 y: viewport.height - height
440 color: control.dividerColor
441 Accessible.ignored: true
Material Design 3 design tokens.