diff options
Diffstat (limited to 'src/quicktemplates/qquicksplitview.cpp')
-rw-r--r-- | src/quicktemplates/qquicksplitview.cpp | 294 |
1 files changed, 197 insertions, 97 deletions
diff --git a/src/quicktemplates/qquicksplitview.cpp b/src/quicktemplates/qquicksplitview.cpp index 0bf1b79cac..1d41016f94 100644 --- a/src/quicktemplates/qquicksplitview.cpp +++ b/src/quicktemplates/qquicksplitview.cpp @@ -225,6 +225,9 @@ Q_LOGGING_CATEGORY(qlcQQuickSplitView, "qt.quick.controls.splitview") Q_LOGGING_CATEGORY(qlcQQuickSplitViewPointer, "qt.quick.controls.splitview.pointer") Q_LOGGING_CATEGORY(qlcQQuickSplitViewState, "qt.quick.controls.splitview.state") +/* + Updates m_fillIndex to be between 0 .. (item count - 1). +*/ void QQuickSplitViewPrivate::updateFillIndex() { const int count = contentModel->count(); @@ -232,10 +235,9 @@ void QQuickSplitViewPrivate::updateFillIndex() qCDebug(qlcQQuickSplitView) << "looking for fillWidth/Height item amongst" << count << "items"; - m_fillIndex = -1; - int i = 0; + int fillIndex = -1; int lastVisibleIndex = -1; - for (; i < count; ++i) { + for (int i = 0; i < count; ++i) { QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(i)); if (!item->isVisible()) continue; @@ -248,19 +250,21 @@ void QQuickSplitViewPrivate::updateFillIndex() continue; if ((horizontal && attached->fillWidth()) || (!horizontal && attached->fillHeight())) { - m_fillIndex = i; - qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << m_fillIndex; + fillIndex = i; + qCDebug(qlcQQuickSplitView) << "found fillWidth/Height item at index" << fillIndex; break; } } - if (m_fillIndex == -1) { - // If there was no item with fillWidth/fillHeight set, m_fillIndex will be -1, - // and we'll set it to the last visible item. + if (fillIndex == -1) { + // If there was no item with fillWidth/fillHeight set, fillIndex will be -1, + // and we'll set m_fillIndex to the last visible item. // If there was an item with fillWidth/fillHeight set, we were already done and this will be skipped. - m_fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; - qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << m_fillIndex; + fillIndex = lastVisibleIndex != -1 ? lastVisibleIndex : count - 1; + qCDebug(qlcQQuickSplitView) << "found no fillWidth/Height item; using last item at index" << fillIndex; } + // Take new fillIndex into use. + m_fillIndex = fillIndex; } /* @@ -325,11 +329,14 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use // The handle shouldn't cross other handles, so use the right edge of // the first handle to the left as the left edge. qreal leftEdge = 0; - if (m_pressedHandleIndex - 1 >= 0) { - const QQuickItem *leftHandle = m_handleItems.at(m_pressedHandleIndex - 1); - leftEdge = horizontal - ? leftHandle->x() + leftHandle->width() - : leftHandle->y() + leftHandle->height(); + for (int i = m_pressedHandleIndex - 1; i >= 0; --i) { + const QQuickItem *nextHandleToTheLeft = m_handleItems.at(i); + if (nextHandleToTheLeft->isVisible()) { + leftEdge = horizontal + ? nextHandleToTheLeft->x() + nextHandleToTheLeft->width() + : nextHandleToTheLeft->y() + nextHandleToTheLeft->height(); + break; + } } // The mouse can be clicked anywhere in the handle, and if we don't account for @@ -344,35 +351,9 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); const qreal newItemSize = newHandlePos - leftEdge; - // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth - // will be used on the next layout (when it's no longer being resized). - if (!attached) { - // Force the attached object to be created since we rely on it. - attached = qobject_cast<QQuickSplitViewAttached*>( - qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); - } - - /* - Users could conceivably respond to size changes in items by setting attached - SplitView properties: - - onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 - - We handle this by doing another layout after the current layout if the - attached/implicit size properties are set during this layout. However, we also - need to set preferredWidth/Height here (for reasons mentioned in the comment above), - but we don't want this to count as a request for a delayed layout, so we guard against it. - */ - m_ignoreNextLayoutRequest = true; - - if (horizontal) - attached->setPreferredWidth(newItemSize); - else - attached->setPreferredHeight(newItemSize); - - // We still need to use requestedWidth in the setWidth() call below, + // We still need to use requestedSize in the width/height call below, // because sizeData has already been calculated and now contains an old - // effectivePreferredWidth value. + // effectivePreferredWidth/Height value. requestedSize = newItemSize; qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized (dragged) " << item @@ -405,22 +386,7 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use const qreal newHandlePos = qBound(leftStop, pressedHandlePos, rightStop); const qreal newItemSize = rightEdge - (newHandlePos + pressedHandleSize); - // Modify the preferredWidth, otherwise the original implicitWidth/preferredWidth - // will be used on the next layout (when it's no longer being resized). - if (!attached) { - // Force the attached object to be created since we rely on it. - attached = qobject_cast<QQuickSplitViewAttached*>( - qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); - } - - m_ignoreNextLayoutRequest = true; - - if (horizontal) - attached->setPreferredWidth(newItemSize); - else - attached->setPreferredHeight(newItemSize); - - // We still need to use requestedSize in the setWidth()/setHeight() call below, + // We still need to use requestedSize in the width/height call below, // because sizeData has already been calculated and now contains an old // effectivePreferredWidth/Height value. requestedSize = newItemSize; @@ -443,34 +409,43 @@ void QQuickSplitViewPrivate::layoutResizeSplitItems(qreal &usedWidth, qreal &use } if (index != m_fillIndex) { + LayoutData layoutData; if (horizontal) { - item->setWidth(qBound( - sizeData.effectiveMinimumWidth, - requestedSize, - sizeData.effectiveMaximumWidth)); - item->setHeight(height); + layoutData.width = qBound( + sizeData.effectiveMinimumWidth, + requestedSize, + sizeData.effectiveMaximumWidth); + layoutData.height = height; } else { - item->setWidth(width); - item->setHeight(qBound( - sizeData.effectiveMinimumHeight, - requestedSize, - sizeData.effectiveMaximumHeight)); + layoutData.width = width; + layoutData.height = qBound( + sizeData.effectiveMinimumHeight, + requestedSize, + sizeData.effectiveMaximumHeight); } - qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized split item " << item - << " (effective" - << " minW=" << sizeData.effectiveMinimumWidth - << ", minH=" << sizeData.effectiveMinimumHeight - << ", prfW=" << sizeData.effectivePreferredWidth - << ", prfH=" << sizeData.effectivePreferredHeight - << ", maxW=" << sizeData.effectiveMaximumWidth - << ", maxH=" << sizeData.effectiveMaximumHeight << ")"; + // Mark that this item has been manually resized. After this + // we can override the preferredWidth & preferredHeight + if (isBeingResized) + layoutData.wasResizedByHandle = true; + + m_layoutData.insert(item, layoutData); + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": calculated the following size data for split item " << item + << ": eminW=" << sizeData.effectiveMinimumWidth + << ", eminH=" << sizeData.effectiveMinimumHeight + << ", eprfW=" << sizeData.effectivePreferredWidth + << ", eprfH=" << sizeData.effectivePreferredHeight + << ", emaxW=" << sizeData.effectiveMaximumWidth + << ", emaxH=" << sizeData.effectiveMaximumHeight + << ", w=" << layoutData.width + << ", h=" << layoutData.height << ""; // Keep track of how much space has been used so far. if (horizontal) - usedWidth += item->width(); + usedWidth += layoutData.width; else - usedHeight += item->height(); + usedHeight += layoutData.height; } else if (indexBeingResizedDueToDrag != m_fillIndex) { // The fill item is resized afterwards, outside of the loop. qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": skipping fill item as we resize it last"; @@ -526,20 +501,26 @@ void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, const QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( qmlAttachedPropertiesObject<QQuickSplitView>(fillItem, false)); const auto fillSizeData = effectiveSizeData(fillItemPrivate, attached); + + LayoutData layoutData; if (isHorizontal()) { - fillItem->setWidth(qBound( - fillSizeData.effectiveMinimumWidth, - width - usedWidth, - fillSizeData.effectiveMaximumWidth)); - fillItem->setHeight(height); + layoutData.width = qBound( + fillSizeData.effectiveMinimumWidth, + width - usedWidth, + fillSizeData.effectiveMaximumWidth); + layoutData.height = height; + usedWidth += layoutData.width; } else { - fillItem->setWidth(width); - fillItem->setHeight(qBound( - fillSizeData.effectiveMinimumHeight, - height - usedHeight, - fillSizeData.effectiveMaximumHeight)); + layoutData.width = width; + layoutData.height = qBound( + fillSizeData.effectiveMinimumHeight, + height - usedHeight, + fillSizeData.effectiveMaximumHeight); + usedHeight += layoutData.height; } + m_layoutData.insert(fillItem, layoutData); + qCDebug(qlcQQuickSplitView).nospace() << " - " << m_fillIndex << ": resized split fill item " << fillItem << " (effective" << " minW=" << fillSizeData.effectiveMinimumWidth @@ -549,6 +530,100 @@ void QQuickSplitViewPrivate::layoutResizeFillItem(QQuickItem *fillItem, } /* + Limit the sizes if needed and apply them into items. +*/ +void QQuickSplitViewPrivate::limitAndApplySizes(qreal usedWidth, qreal usedHeight) +{ + const int count = contentModel->count(); + const bool horizontal = isHorizontal(); + + const qreal maxSize = horizontal ? width : height; + const qreal usedSize = horizontal ? usedWidth : usedHeight; + if (usedSize > maxSize) { + qCDebug(qlcQQuickSplitView).nospace() << "usedSize " << usedSize << " is greater than maxSize " + << maxSize << "; reducing size of non-filled items from right to left / bottom to top"; + + // If items don't fit, reduce the size of non-filled items from + // right to left / bottom to top. At this point filled item is + // already at its minimum size or usedSize wouldn't be > maxSize. + qreal delta = usedSize - maxSize; + for (int index = count - 1; index >= 0; --index) { + if (index == m_fillIndex) + continue; + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) + continue; + + const QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + const auto sizeData = effectiveSizeData(itemPrivate, attached); + const qreal maxReduce = horizontal ? + m_layoutData[item].width - sizeData.effectiveMinimumWidth : + m_layoutData[item].height - sizeData.effectiveMinimumHeight; + + const qreal reduce = std::min(maxReduce, delta); + if (horizontal) + m_layoutData[item].width -= reduce; + else + m_layoutData[item].height -= reduce; + + delta -= reduce; + if (delta <= 0) { + // Now all the items fit, so continue + break; + } + } + } + + qCDebug(qlcQQuickSplitView).nospace() << " applying new sizes to " << count << " items (excluding hidden items)"; + + // Apply the new sizes into items + for (int index = 0; index < count; ++index) { + QQuickItem *item = qobject_cast<QQuickItem*>(contentModel->object(index)); + if (!item->isVisible()) + continue; + + QQuickSplitViewAttached *attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, false)); + LayoutData layoutData = m_layoutData.value(item); + if (layoutData.wasResizedByHandle) { + // Modify the preferredWidth/Height, otherwise the original implicit/preferred size + // will be used on the next layout (when it's no longer being resized). + if (!attached) { + // Force the attached object to be created since we rely on it. + attached = qobject_cast<QQuickSplitViewAttached*>( + qmlAttachedPropertiesObject<QQuickSplitView>(item, true)); + } + /* + Users could conceivably respond to size changes in items by setting attached + SplitView properties: + + onWidthChanged: if (width < 10) secondItem.SplitView.preferredWidth = 100 + + We handle this by doing another layout after the current layout if the + attached/implicit size properties are set during this layout. However, we also + need to set preferredWidth/Height here, otherwise the original implicit/preferred sizes + will be used on the next layout (when it's no longer being resized). + But we don't want this to count as a request for a delayed layout, so we guard against it. + */ + m_ignoreNextLayoutRequest = true; + if (horizontal) + attached->setPreferredWidth(layoutData.width); + else + attached->setPreferredHeight(layoutData.height); + } + + qCDebug(qlcQQuickSplitView).nospace() << " - " << index << ": resized item " << item << " from " + << item->width() << "x" << item->height() << " to " + << layoutData.width << "x" << layoutData.height; + + item->setWidth(layoutData.width); + item->setHeight(layoutData.height); + } +} + +/* Positions items by laying them out in a row or column. Items that aren't visible are skipped. @@ -621,6 +696,19 @@ void QQuickSplitViewPrivate::requestLayout() q->polish(); } +/* + Layout steps are (horizontal SplitView as an example): + 1) layoutResizeSplitItems: Gives each non-filled item its preferredWidth + or if not set, implicitWidth. Sizes are kept between effectiveMinimumWidth + and effectiveMaximumWidth and stored into layoutData for now. + 2) layoutResizeFillItem: Gives filled item all the remaining space. Size is + kept between effectiveMinimumWidth and effectiveMaximumWidth and stored + into layoutData for now. + 3) limitAndApplySizes: If we have used more space than SplitView item has, + start reducing non-filled item sizes from right-to-left. Reduce them up + to minimumWidth or until SplitView item width is reached. Finally set the + new item sizes from layoutData. +*/ void QQuickSplitViewPrivate::layout() { if (!componentComplete) @@ -648,13 +736,15 @@ void QQuickSplitViewPrivate::layout() qCDebug(qlcQQuickSplitView) << "laying out" << count << "split items" << (horizontal ? "horizontally" : "vertically") << "in SplitView" << q_func(); + // Total sizes of items used during the layout operation. qreal usedWidth = 0; qreal usedHeight = 0; int indexBeingResizedDueToDrag = -1; + m_layoutData.clear(); qCDebug(qlcQQuickSplitView) << " resizing:"; - // First, resize the items. We need to do this first because otherwise fill + // First, resize the non-filled items. We need to do this first because otherwise fill // items would take up all of the remaining space as soon as they are encountered. layoutResizeSplitItems(usedWidth, usedHeight, indexBeingResizedDueToDrag); @@ -666,6 +756,9 @@ void QQuickSplitViewPrivate::layout() QQuickItem *fillItem = qobject_cast<QQuickItem*>(contentModel->object(m_fillIndex)); layoutResizeFillItem(fillItem, usedWidth, usedHeight, indexBeingResizedDueToDrag); + // Reduce the sizes still if needed and apply them into items. + limitAndApplySizes(usedWidth, usedHeight); + qCDebug(qlcQQuickSplitView) << " positioning:"; // Position the items. @@ -1129,13 +1222,19 @@ void QQuickSplitView::setOrientation(Qt::Orientation orientation) return; d->m_orientation = orientation; - d->resizeHandles(); + #if QT_CONFIG(cursor) for (QQuickItem *handleItem : d->m_handleItems) d->updateCursorHandle(handleItem); #endif - d->requestLayout(); emit orientationChanged(); + + // Do this after emitting orientationChanged so that the bindings in QML + // update the implicit size in time. + d->resizeHandles(); + // This is queued (via polish) anyway, but to make our intentions clear, + // do it afterwards too. + d->requestLayout(); } /*! @@ -1359,7 +1458,6 @@ void QQuickSplitView::componentComplete() { Q_D(QQuickSplitView); QQuickControl::componentComplete(); - d->resizeHandles(); d->updateFillIndex(); d->updatePolish(); } @@ -1912,9 +2010,10 @@ void QQuickSplitViewAttached::resetMaximumHeight() This attached property controls whether the item takes the remaining space in the split view after all other items have been laid out. - By default, the last visible child of the split view will have this set, + By default, the last visible child of the split view will fill the view, but it can be changed by explicitly setting \c fillWidth to \c true on - another item. + another item. If multiple items have \c fillWidth set to \c true, the + left-most item will fill the view. The width of a split item with \c fillWidth set to \c true is still restricted within its \l minimumWidth and \l maximumWidth. @@ -1947,9 +2046,10 @@ void QQuickSplitViewAttached::setFillWidth(bool fill) This attached property controls whether the item takes the remaining space in the split view after all other items have been laid out. - By default, the last visible child of the split view will have this set, + By default, the last visible child of the split view will fill the view, but it can be changed by explicitly setting \c fillHeight to \c true on - another item. + another item. If multiple items have \c fillHeight set to \c true, the + top-most item will fill the view. The height of a split item with \c fillHeight set to \c true is still restricted within its \l minimumHeight and \l maximumHeight. |