7 #include "toolbarlayout.h"
10 #include <unordered_map>
12 #include <QDeadlineTimer>
13 #include <QQmlComponent>
17 #include "loggingcategory.h"
18 #include "toolbarlayoutdelegate.h"
20 ToolBarLayoutAttached::ToolBarLayoutAttached(
QObject *parent)
30 void ToolBarLayoutAttached::setAction(
QObject *action)
35 class ToolBarLayout::Private
45 ToolBarLayoutDelegate *createDelegate(
QObject *action);
46 qreal layoutStart(qreal layoutWidth);
47 void maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth);
52 ActionsProperty actionsProperty;
59 qreal visibleWidth = 0.0;
61 HeightMode heightMode = ConstrainIfLarger;
63 bool completed =
false;
64 bool layoutQueued =
false;
65 bool actionsChanged =
false;
66 std::unordered_map<QObject *, std::unique_ptr<ToolBarLayoutDelegate>> delegates;
69 ToolBarDelegateIncubator *moreButtonIncubator =
nullptr;
70 bool shouldShowMoreButton =
false;
71 int firstHiddenIndex = -1;
74 QTimer *removalTimer =
nullptr;
79 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
89 ToolBarLayout::ToolBarLayout(
QQuickItem *parent)
91 , d(
std::make_unique<Private>(this))
93 d->actionsProperty = ActionsProperty(
this,
this, Private::appendAction, Private::actionCount, Private::action, Private::clearActions);
98 d->removalTimer =
new QTimer{
this};
100 d->removalTimer->setSingleShot(
true);
102 for (auto action : std::as_const(d->removedActions)) {
103 if (!d->actions.contains(action)) {
104 d->delegates.erase(action);
107 d->removedActions.clear();
111 ToolBarLayout::~ToolBarLayout()
117 return d->actionsProperty;
122 d->actions.append(action);
123 d->actionsChanged =
true;
126 auto itr = d->delegates.find(action);
127 if (itr != d->delegates.end()) {
128 d->delegates.erase(itr);
131 d->actions.removeOne(action);
132 d->actionsChanged =
true;
142 auto itr = d->delegates.find(action);
143 if (itr != d->delegates.end()) {
147 d->actions.removeOne(action);
148 d->removedActions.append(action);
149 d->removalTimer->start();
150 d->actionsChanged =
true;
157 for (
auto action : std::as_const(d->actions)) {
158 auto itr = d->delegates.find(action);
159 if (itr != d->delegates.end()) {
164 d->removedActions.append(d->actions);
166 d->actionsChanged =
true;
173 return d->hiddenActions;
178 return d->fullDelegate;
181 void ToolBarLayout::setFullDelegate(
QQmlComponent *newFullDelegate)
183 if (newFullDelegate == d->fullDelegate) {
187 d->fullDelegate = newFullDelegate;
188 d->delegates.clear();
190 Q_EMIT fullDelegateChanged();
195 return d->iconDelegate;
198 void ToolBarLayout::setIconDelegate(
QQmlComponent *newIconDelegate)
200 if (newIconDelegate == d->iconDelegate) {
204 d->iconDelegate = newIconDelegate;
205 d->delegates.clear();
207 Q_EMIT iconDelegateChanged();
212 return d->moreButton;
215 void ToolBarLayout::setMoreButton(
QQmlComponent *newMoreButton)
217 if (newMoreButton == d->moreButton) {
221 d->moreButton = newMoreButton;
222 if (d->moreButtonInstance) {
224 d->moreButtonInstance =
nullptr;
227 Q_EMIT moreButtonChanged();
235 void ToolBarLayout::setSpacing(qreal newSpacing)
237 if (newSpacing == d->spacing) {
241 d->spacing = newSpacing;
243 Q_EMIT spacingChanged();
253 if (newAlignment == d->alignment) {
257 d->alignment = newAlignment;
259 Q_EMIT alignmentChanged();
264 return d->visibleWidth;
269 return d->moreButtonInstance ? d->moreButtonInstance->width() : 0;
274 return d->layoutDirection;
279 if (newLayoutDirection == d->layoutDirection) {
283 d->layoutDirection = newLayoutDirection;
285 Q_EMIT layoutDirectionChanged();
290 return d->heightMode;
293 void ToolBarLayout::setHeightMode(HeightMode newHeightMode)
295 if (newHeightMode == d->heightMode) {
299 d->heightMode = newHeightMode;
301 Q_EMIT heightModeChanged();
311 void ToolBarLayout::componentComplete()
318 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
321 void ToolBarLayout::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
324 if (newGeometry != oldGeometry) {
327 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
330 QQuickItem::geometryChange(newGeometry, oldGeometry);
336 if (change == ItemVisibleHasChanged || change == ItemSceneChange) {
342 void ToolBarLayout::updatePolish()
347 void ToolBarLayout::Private::performLayout()
349 if (!fullDelegate || !iconDelegate || !moreButton) {
350 qCWarning(KirigamiLog) <<
"ToolBarLayout: Unable to layout, required properties are not set";
354 if (actions.isEmpty()) {
355 q->setImplicitWidth(0);
356 q->setImplicitHeight(0);
360 hiddenActions.clear();
361 firstHiddenIndex = -1;
363 sortedDelegates = createDelegates();
365 bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](
const std::pair<
QObject *
const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
366 return entry.second->isReady();
368 if (!ready || !moreButtonInstance) {
372 qreal maxHeight = moreButtonInstance->isVisible() ? moreButtonInstance->height() : 0.0;
373 qreal maxWidth = 0.0;
378 for (
auto entry : std::as_const(sortedDelegates)) {
379 if (!entry->isActionVisible()) {
384 if (entry->isHidden()) {
386 hiddenActions.append(entry->action());
390 if (entry->isIconOnly()) {
396 maxWidth += entry->width() + spacing;
397 maxHeight = std::max(maxHeight, entry->maxHeight());
403 if (q->heightValid() && q->height() > 0.0) {
404 maxHeight = q->height();
407 qreal visibleActionsWidth = 0.0;
409 if (maxWidth > q->width() - (hiddenActions.isEmpty() ? 0.0 : moreButtonInstance->width() + spacing)) {
412 qreal layoutWidth = q->width() - (moreButtonInstance->width() + spacing);
417 layoutWidth -= (moreButtonInstance->width() + spacing);
420 for (
int i = 0; i < sortedDelegates.size(); ++i) {
421 auto delegate = sortedDelegates.at(i);
423 maybeHideDelegate(i, visibleActionsWidth, layoutWidth);
425 if (delegate->isVisible()) {
426 visibleActionsWidth += delegate->width() + spacing;
429 if (!qFuzzyIsNull(visibleActionsWidth)) {
431 visibleActionsWidth -= spacing;
434 visibleActionsWidth = maxWidth;
437 if (!hiddenActions.isEmpty()) {
439 moreButtonInstance->setX(q->width() - moreButtonInstance->width());
441 moreButtonInstance->setX(0.0);
444 if (heightMode == AlwaysFill) {
445 moreButtonInstance->setHeight(q->height());
446 }
else if (heightMode == ConstrainIfLarger) {
447 if (moreButtonInstance->implicitHeight() > maxHeight) {
448 moreButtonInstance->setHeight(maxHeight);
450 moreButtonInstance->setHeight(moreButtonInstance->implicitHeight());
454 moreButtonInstance->setY(qRound((maxHeight - moreButtonInstance->height()) / 2.0));
455 shouldShowMoreButton =
true;
456 moreButtonInstance->setVisible(
true);
458 shouldShowMoreButton =
false;
459 moreButtonInstance->setVisible(
false);
462 qreal currentX = layoutStart(visibleActionsWidth);
463 for (
auto entry : std::as_const(sortedDelegates)) {
464 if (!entry->isVisible()) {
468 if (heightMode == AlwaysFill) {
469 entry->setHeight(q->height());
470 }
else if (heightMode == ConstrainIfLarger) {
471 if (entry->implicitHeight() > maxHeight) {
472 entry->setHeight(maxHeight);
474 entry->setHeight(entry->implicitHeight());
478 qreal y = qRound((maxHeight - entry->height()) / 2.0);
481 entry->setPosition(currentX, y);
482 currentX += entry->width() + spacing;
484 entry->setPosition(currentX - entry->width(), y);
485 currentX -= entry->width() + spacing;
491 q->setImplicitSize(maxWidth, maxHeight);
492 Q_EMIT q->hiddenActionsChanged();
494 qreal newVisibleWidth = visibleActionsWidth;
495 if (moreButtonInstance->isVisible()) {
496 newVisibleWidth += moreButtonInstance->width() + (newVisibleWidth > 0.0 ? spacing : 0.0);
498 if (!qFuzzyCompare(newVisibleWidth, visibleWidth)) {
499 visibleWidth = newVisibleWidth;
500 Q_EMIT q->visibleWidthChanged();
503 if (actionsChanged) {
507 Q_EMIT q->actionsChanged();
508 actionsChanged =
false;
511 sortedDelegates.clear();
517 for (
auto action : std::as_const(actions)) {
518 if (delegates.find(action) != delegates.end()) {
519 result.
append(delegates.at(action).get());
521 auto delegate = std::unique_ptr<ToolBarLayoutDelegate>(createDelegate(action));
523 result.
append(delegate.get());
524 delegates.emplace(action, std::move(delegate));
529 if (!moreButtonInstance && !moreButtonIncubator) {
530 moreButtonIncubator =
new ToolBarDelegateIncubator(moreButton, qmlContext(moreButton));
531 moreButtonIncubator->setStateCallback([
this](
QQuickItem *item) {
534 moreButtonIncubator->setCompletedCallback([
this](ToolBarDelegateIncubator *incubator) {
535 moreButtonInstance = qobject_cast<QQuickItem *>(incubator->object());
536 moreButtonInstance->setVisible(
false);
539 moreButtonInstance->setVisible(shouldShowMoreButton);
542 Q_EMIT q->minimumWidthChanged();
545 Q_EMIT q->minimumWidthChanged();
548 delete moreButtonIncubator;
549 moreButtonIncubator =
nullptr;
552 moreButtonIncubator->create();
558 ToolBarLayoutDelegate *ToolBarLayout::Private::createDelegate(
QObject *action)
561 auto displayComponent = action->
property(
"displayComponent");
562 if (displayComponent.isValid()) {
566 if (!fullComponent) {
567 fullComponent = fullDelegate;
570 auto result =
new ToolBarLayoutDelegate(q);
571 result->setAction(action);
572 result->createItems(fullComponent, iconDelegate, [
this, action](
QQuickItem *newItem) {
574 auto attached =
static_cast<ToolBarLayoutAttached *
>(qmlAttachedPropertiesObject<ToolBarLayout>(newItem,
true));
575 attached->setAction(action);
581 qreal ToolBarLayout::Private::layoutStart(qreal layoutWidth)
583 qreal availableWidth = moreButtonInstance->isVisible() ? q->width() - (moreButtonInstance->width() + spacing) : q->width();
588 return (q->width() / 2) + (layoutDirection ==
Qt::LeftToRight ? -layoutWidth / 2.0 : layoutWidth / 2.0);
590 qreal offset = availableWidth - layoutWidth;
591 return layoutDirection ==
Qt::LeftToRight ? offset : q->width() - offset;
596 void ToolBarLayout::Private::maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth)
598 auto delegate = sortedDelegates.at(index);
600 if (!delegate->isVisible()) {
605 if (currentWidth + delegate->width() < totalWidth && (firstHiddenIndex < 0 || index < firstHiddenIndex)) {
611 if (delegate->isKeepVisible()) {
617 if (currentWidth + delegate->iconWidth() > totalWidth) {
619 for (
auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
620 auto previousDelegate = sortedDelegates.at(currentIndex);
621 if (!previousDelegate->isVisible() || previousDelegate->isKeepVisible()) {
625 auto width = previousDelegate->width();
626 previousDelegate->hide();
627 hiddenActions.append(previousDelegate->action());
628 currentWidth -= (width + spacing);
630 if (currentWidth + delegate->fullWidth() <= totalWidth) {
631 delegate->showFull();
633 }
else if (currentWidth + delegate->iconWidth() <= totalWidth) {
634 delegate->showIcon();
639 if (currentWidth + delegate->width() <= totalWidth) {
645 for (
auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
646 auto previousDelegate = sortedDelegates.at(currentIndex);
647 if (!previousDelegate->isVisible() || !previousDelegate->isKeepVisible()) {
651 auto extraSpace = previousDelegate->width() - previousDelegate->iconWidth();
652 previousDelegate->showIcon();
653 currentWidth -= extraSpace;
655 if (currentWidth + delegate->fullWidth() <= totalWidth) {
656 delegate->showFull();
658 }
else if (currentWidth + delegate->iconWidth() <= totalWidth) {
659 delegate->showIcon();
665 if (currentWidth + delegate->width() > totalWidth) {
667 hiddenActions.append(delegate->action());
670 delegate->showIcon();
676 hiddenActions.append(delegate->action());
680 if (firstHiddenIndex < 0) {
681 firstHiddenIndex = index;
692 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
701 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)