7#include "toolbarlayout.h"
10#include <unordered_map>
12#include <QDeadlineTimer>
13#include <QQmlComponent>
16#include "loggingcategory.h"
17#include "toolbarlayoutdelegate.h"
19ToolBarLayoutAttached::ToolBarLayoutAttached(
QObject *parent)
29void ToolBarLayoutAttached::setAction(
QObject *action)
34class ToolBarLayoutPrivate
43 ~ToolBarLayoutPrivate()
45 if (moreButtonIncubator) {
46 moreButtonIncubator->
clear();
47 delete moreButtonIncubator;
51 void calculateImplicitSize();
54 ToolBarLayoutDelegate *createDelegate(
QObject *action);
55 qreal layoutStart(qreal layoutWidth);
56 void maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth);
67 qreal visibleActionsWidth = 0.0;
68 qreal visibleWidth = 0.0;
72 bool completed =
false;
73 bool actionsChanged =
false;
74 bool implicitSizeValid =
false;
76 std::unordered_map<QObject *, std::unique_ptr<ToolBarLayoutDelegate>> delegates;
79 ToolBarDelegateIncubator *moreButtonIncubator =
nullptr;
80 bool shouldShowMoreButton =
false;
81 int firstHiddenIndex = -1;
84 QTimer *removalTimer =
nullptr;
94ToolBarLayout::ToolBarLayout(
QQuickItem *parent)
96 , d(std::make_unique<ToolBarLayoutPrivate>(this))
98 d->actionsProperty = ActionsProperty(
this,
100 ToolBarLayoutPrivate::appendAction,
101 ToolBarLayoutPrivate::actionCount,
102 ToolBarLayoutPrivate::action,
103 ToolBarLayoutPrivate::clearActions);
108 d->removalTimer =
new QTimer{
this};
110 d->removalTimer->setSingleShot(
true);
112 for (auto action : std::as_const(d->removedActions)) {
113 if (!d->actions.contains(action)) {
114 d->delegates.erase(action);
117 d->removedActions.clear();
121ToolBarLayout::~ToolBarLayout()
127 return d->actionsProperty;
132 if (action ==
nullptr) {
135 d->actions.append(action);
136 d->actionsChanged =
true;
139 auto itr = d->delegates.find(action);
140 if (itr != d->delegates.end()) {
141 d->delegates.erase(itr);
144 d->actions.removeOne(action);
145 d->actionsChanged =
true;
155 auto itr = d->delegates.find(action);
156 if (itr != d->delegates.end()) {
160 d->actions.removeOne(action);
161 d->removedActions.append(action);
162 d->removalTimer->start();
163 d->actionsChanged =
true;
170 for (
auto action : std::as_const(d->actions)) {
171 auto itr = d->delegates.find(action);
172 if (itr != d->delegates.end()) {
177 d->removedActions.append(d->actions);
179 d->actionsChanged =
true;
186 return d->hiddenActions;
191 return d->fullDelegate;
194void ToolBarLayout::setFullDelegate(
QQmlComponent *newFullDelegate)
196 if (newFullDelegate == d->fullDelegate) {
200 d->fullDelegate = newFullDelegate;
201 d->delegates.clear();
203 Q_EMIT fullDelegateChanged();
208 return d->iconDelegate;
211void ToolBarLayout::setIconDelegate(
QQmlComponent *newIconDelegate)
213 if (newIconDelegate == d->iconDelegate) {
217 d->iconDelegate = newIconDelegate;
218 d->delegates.clear();
220 Q_EMIT iconDelegateChanged();
225 return d->separatorDelegate;
228void ToolBarLayout::setSeparatorDelegate(
QQmlComponent *newSeparatorDelegate)
230 if (newSeparatorDelegate == d->separatorDelegate) {
234 d->separatorDelegate = newSeparatorDelegate;
235 d->delegates.clear();
237 Q_EMIT separatorDelegateChanged();
242 return d->moreButton;
245void ToolBarLayout::setMoreButton(
QQmlComponent *newMoreButton)
247 if (newMoreButton == d->moreButton) {
251 d->moreButton = newMoreButton;
252 if (d->moreButtonInstance) {
253 d->moreButtonInstance->deleteLater();
254 d->moreButtonInstance =
nullptr;
257 Q_EMIT moreButtonChanged();
265void ToolBarLayout::setSpacing(qreal newSpacing)
267 if (newSpacing == d->spacing) {
271 d->spacing = newSpacing;
283 if (newAlignment == d->alignment) {
287 d->alignment = newAlignment;
289 Q_EMIT alignmentChanged();
294 return d->visibleWidth;
299 return d->moreButtonInstance ? d->moreButtonInstance->width() : 0;
304 return d->layoutDirection;
309 if (newLayoutDirection == d->layoutDirection) {
313 d->layoutDirection = newLayoutDirection;
315 Q_EMIT layoutDirectionChanged();
320 return d->heightMode;
323void ToolBarLayout::setHeightMode(HeightMode newHeightMode)
325 if (newHeightMode == d->heightMode) {
329 d->heightMode = newHeightMode;
331 Q_EMIT heightModeChanged();
336 d->implicitSizeValid =
false;
340void ToolBarLayout::componentComplete()
347void ToolBarLayout::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
349 if (newGeometry != oldGeometry) {
350 if (newGeometry.
size() !=
QSizeF{implicitWidth(), implicitHeight()}) {
367void ToolBarLayout::updatePolish()
386void ToolBarLayoutPrivate::calculateImplicitSize()
392 if (!fullDelegate || !iconDelegate || !separatorDelegate || !moreButton) {
393 qCWarning(KirigamiLayoutsLog) <<
"ToolBarLayout: Unable to layout, required properties are not set";
398 q->setImplicitSize(0., 0.);
402 hiddenActions.
clear();
403 firstHiddenIndex = -1;
405 sortedDelegates = createDelegates();
407 bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](
const std::pair<
QObject *
const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
408 return entry.second->isReady();
410 if (!ready || !moreButtonInstance) {
414 qreal maxHeight = 0.0;
415 qreal maxWidth = 0.0;
420 for (
auto entry : std::as_const(sortedDelegates)) {
421 if (!entry->isActionVisible()) {
426 if (entry->isHidden()) {
428 hiddenActions.
append(entry->action());
432 if (entry->isIconOnly()) {
438 maxWidth += entry->width() + spacing;
439 maxHeight = std::max(maxHeight, entry->maxHeight());
445 visibleActionsWidth = 0.0;
447 if (maxWidth > q->
width() - (hiddenActions.
isEmpty() ? 0.0 : moreButtonInstance->
width() + spacing)) {
450 qreal layoutWidth = q->
width() - (moreButtonInstance->
width() + spacing);
455 layoutWidth -= (moreButtonInstance->
width() + spacing);
458 for (
int i = 0; i < sortedDelegates.
size(); ++i) {
459 auto delegate = sortedDelegates.
at(i);
461 maybeHideDelegate(i, visibleActionsWidth, layoutWidth);
463 if (delegate->isVisible()) {
464 visibleActionsWidth += delegate->width() + spacing;
467 if (!qFuzzyIsNull(visibleActionsWidth)) {
469 visibleActionsWidth -= spacing;
472 visibleActionsWidth = maxWidth;
475 if (!hiddenActions.
isEmpty()) {
476 maxHeight = std::max(maxHeight, moreButtonInstance->
implicitHeight());
479 q->setImplicitSize(maxWidth, maxHeight);
480 Q_EMIT q->hiddenActionsChanged();
482 implicitSizeValid =
true;
487void ToolBarLayoutPrivate::performLayout()
489 if (!completed || actions.
isEmpty()) {
493 if (!implicitSizeValid) {
494 calculateImplicitSize();
497 if (sortedDelegates.
isEmpty()) {
498 sortedDelegates = createDelegates();
501 bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](
const std::pair<
QObject *
const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
502 return entry.second->isReady();
504 if (!ready || !moreButtonInstance) {
508 qreal width = q->
width();
509 qreal height = q->
height();
511 if (!hiddenActions.
isEmpty()) {
513 moreButtonInstance->
setX(width - moreButtonInstance->
width());
515 moreButtonInstance->
setX(0.0);
530 moreButtonInstance->
setY(qRound((height - moreButtonInstance->
height()) / 2.0));
531 shouldShowMoreButton =
true;
534 shouldShowMoreButton =
false;
538 qreal currentX = layoutStart(visibleActionsWidth);
539 for (
auto entry : std::as_const(sortedDelegates)) {
540 if (!entry->isVisible()) {
545 entry->setHeight(height);
547 if (entry->implicitHeight() > height) {
548 entry->setHeight(height);
550 entry->resetHeight();
553 entry->resetHeight();
556 qreal y = qRound((height - entry->height()) / 2.0);
559 entry->setPosition(currentX, y);
560 currentX += entry->width() + spacing;
562 entry->setPosition(currentX - entry->width(), y);
563 currentX -= entry->width() + spacing;
569 qreal newVisibleWidth = visibleActionsWidth;
571 newVisibleWidth += moreButtonInstance->
width() + (newVisibleWidth > 0.0 ? spacing : 0.0);
573 if (!qFuzzyCompare(newVisibleWidth, visibleWidth)) {
574 visibleWidth = newVisibleWidth;
575 Q_EMIT q->visibleWidthChanged();
578 if (actionsChanged) {
582 Q_EMIT q->actionsChanged();
583 actionsChanged =
false;
586 sortedDelegates.
clear();
592 for (
auto action : std::as_const(actions)) {
593 if (delegates.find(action) != delegates.end()) {
594 result.
append(delegates.at(action).get());
596 auto delegate = std::unique_ptr<ToolBarLayoutDelegate>(createDelegate(action));
598 result.
append(delegate.get());
599 delegates.emplace(action, std::move(delegate));
604 if (!moreButtonInstance && !moreButtonIncubator) {
605 moreButtonIncubator =
new ToolBarDelegateIncubator(moreButton, qmlContext(moreButton));
606 moreButtonIncubator->setStateCallback([
this](
QQuickItem *item) {
609 moreButtonIncubator->setCompletedCallback([
this](ToolBarDelegateIncubator *incubator) {
610 moreButtonInstance = qobject_cast<QQuickItem *>(incubator->
object());
614 moreButtonInstance->
setVisible(shouldShowMoreButton);
618 Q_EMIT q->minimumWidthChanged();
621 delete moreButtonIncubator;
622 moreButtonIncubator =
nullptr;
625 moreButtonIncubator->create();
631ToolBarLayoutDelegate *ToolBarLayoutPrivate::createDelegate(
QObject *action)
634 auto displayComponent = action->
property(
"displayComponent");
635 if (displayComponent.isValid()) {
639 if (!fullComponent) {
640 fullComponent = fullDelegate;
643 auto separator = action->
property(
"separator");
644 if (separator.isValid() && separator.toBool()) {
645 fullComponent = separatorDelegate;
648 auto result =
new ToolBarLayoutDelegate(q);
649 result->setAction(action);
650 result->createItems(fullComponent, iconDelegate, [
this, action](
QQuickItem *newItem) {
652 auto attached =
static_cast<ToolBarLayoutAttached *
>(qmlAttachedPropertiesObject<ToolBarLayout>(newItem,
true));
653 attached->setAction(action);
668qreal ToolBarLayoutPrivate::layoutStart(qreal layoutWidth)
670 qreal availableWidth = moreButtonInstance->
isVisible() ? q->
width() - (moreButtonInstance->
width() + spacing) : q->
width();
675 return (q->
width() / 2) + (layoutDirection ==
Qt::LeftToRight ? -layoutWidth / 2.0 : layoutWidth / 2.0);
677 qreal offset = availableWidth - layoutWidth;
683void ToolBarLayoutPrivate::maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth)
685 auto delegate = sortedDelegates.
at(index);
687 if (!delegate->isVisible()) {
692 if (currentWidth + delegate->width() < totalWidth && (firstHiddenIndex < 0 || index < firstHiddenIndex)) {
698 if (delegate->isKeepVisible()) {
704 if (currentWidth + delegate->iconWidth() > totalWidth) {
706 for (
auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
707 auto previousDelegate = sortedDelegates.
at(currentIndex);
708 if (!previousDelegate->isVisible() || previousDelegate->isKeepVisible()) {
712 auto width = previousDelegate->width();
713 previousDelegate->hide();
714 hiddenActions.
append(previousDelegate->action());
715 currentWidth -= (width + spacing);
717 if (currentWidth + delegate->fullWidth() <= totalWidth) {
718 delegate->showFull();
720 }
else if (currentWidth + delegate->iconWidth() <= totalWidth) {
721 delegate->showIcon();
726 if (currentWidth + delegate->width() <= totalWidth) {
732 for (
auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
733 auto previousDelegate = sortedDelegates.
at(currentIndex);
734 if (!previousDelegate->isVisible() || !previousDelegate->isKeepVisible()) {
738 auto extraSpace = previousDelegate->width() - previousDelegate->iconWidth();
739 previousDelegate->showIcon();
740 currentWidth -= extraSpace;
742 if (currentWidth + delegate->fullWidth() <= totalWidth) {
743 delegate->showFull();
745 }
else if (currentWidth + delegate->iconWidth() <= totalWidth) {
746 delegate->showIcon();
752 if (currentWidth + delegate->width() > totalWidth) {
754 hiddenActions.
append(delegate->action());
757 delegate->showIcon();
763 hiddenActions.
append(delegate->action());
767 if (firstHiddenIndex < 0) {
768 firstHiddenIndex = index;
794#include "moc_toolbarlayout.cpp"
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
QVariant property(const char *name) const const
QObject * object() const const
QList< QQuickItem * > childItems() const const
virtual void componentComplete() override
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
virtual void itemChange(ItemChange change, const ItemChangeData &value)
void setParentItem(QQuickItem *parent)
void stackBefore(const QQuickItem *sibling)
QSizeF size() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setInterval(int msec)