7#include "toolbarlayout.h"
10#include <unordered_map>
12#include <QDeadlineTimer>
13#include <QElapsedTimer>
14#include <QQmlComponent>
17#include "loggingcategory.h"
18#include "toolbarlayoutdelegate.h"
20ToolBarLayoutAttached::ToolBarLayoutAttached(
QObject *parent)
30void ToolBarLayoutAttached::setAction(
QObject *action)
35class ToolBarLayoutPrivate
37 ToolBarLayout *
const q;
40 ToolBarLayoutPrivate(ToolBarLayout *qq)
44 ~ToolBarLayoutPrivate()
46 if (moreButtonIncubator) {
47 moreButtonIncubator->clear();
48 delete moreButtonIncubator;
52 void calculateImplicitSize();
54 QList<ToolBarLayoutDelegate *> createDelegates();
55 ToolBarLayoutDelegate *createDelegate(QObject *action);
56 qreal layoutStart(qreal layoutWidth);
57 void maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth);
59 QList<QObject *> actions;
60 ToolBarLayout::ActionsProperty actionsProperty;
61 QList<QObject *> hiddenActions;
62 QQmlComponent *fullDelegate =
nullptr;
63 QQmlComponent *iconDelegate =
nullptr;
64 QQmlComponent *separatorDelegate =
nullptr;
65 QQmlComponent *moreButton =
nullptr;
68 qreal visibleActionsWidth = 0.0;
69 qreal visibleWidth = 0.0;
73 bool completed =
false;
74 bool actionsChanged =
false;
75 bool implicitSizeValid =
false;
77 std::unordered_map<QObject *, std::unique_ptr<ToolBarLayoutDelegate>> delegates;
78 QList<ToolBarLayoutDelegate *> sortedDelegates;
79 QQuickItem *moreButtonInstance =
nullptr;
80 ToolBarDelegateIncubator *moreButtonIncubator =
nullptr;
81 bool shouldShowMoreButton =
false;
82 int firstHiddenIndex = -1;
84 QList<QObject *> removedActions;
85 QTimer *removalTimer =
nullptr;
87 QElapsedTimer performanceTimer;
89 static void appendAction(ToolBarLayout::ActionsProperty *list, QObject *action);
90 static qsizetype actionCount(ToolBarLayout::ActionsProperty *list);
91 static QObject *action(ToolBarLayout::ActionsProperty *list, qsizetype index);
92 static void clearActions(ToolBarLayout::ActionsProperty *list);
95ToolBarLayout::ToolBarLayout(
QQuickItem *parent)
97 , d(std::make_unique<ToolBarLayoutPrivate>(this))
99 d->actionsProperty = ActionsProperty(
this,
101 ToolBarLayoutPrivate::appendAction,
102 ToolBarLayoutPrivate::actionCount,
103 ToolBarLayoutPrivate::action,
104 ToolBarLayoutPrivate::clearActions);
109 d->removalTimer =
new QTimer{
this};
111 d->removalTimer->setSingleShot(
true);
113 for (auto action : std::as_const(d->removedActions)) {
114 if (!d->actions.contains(action)) {
115 d->delegates.erase(action);
118 d->removedActions.clear();
122ToolBarLayout::~ToolBarLayout()
126ToolBarLayout::ActionsProperty ToolBarLayout::actionsProperty()
const
128 return d->actionsProperty;
133 if (action ==
nullptr) {
136 d->actions.append(action);
137 d->actionsChanged =
true;
140 auto itr = d->delegates.find(action);
141 if (itr != d->delegates.end()) {
142 d->delegates.erase(itr);
145 d->actions.removeOne(action);
146 d->actionsChanged =
true;
156 auto itr = d->delegates.find(action);
157 if (itr != d->delegates.end()) {
161 d->actions.removeOne(action);
162 d->removedActions.append(action);
163 d->removalTimer->start();
164 d->actionsChanged =
true;
171 for (
auto action : std::as_const(d->actions)) {
172 auto itr = d->delegates.find(action);
173 if (itr != d->delegates.end()) {
178 d->removedActions.append(d->actions);
180 d->actionsChanged =
true;
187 return d->hiddenActions;
192 return d->fullDelegate;
195void ToolBarLayout::setFullDelegate(
QQmlComponent *newFullDelegate)
197 if (newFullDelegate == d->fullDelegate) {
201 d->fullDelegate = newFullDelegate;
202 d->delegates.clear();
204 Q_EMIT fullDelegateChanged();
209 return d->iconDelegate;
212void ToolBarLayout::setIconDelegate(
QQmlComponent *newIconDelegate)
214 if (newIconDelegate == d->iconDelegate) {
218 d->iconDelegate = newIconDelegate;
219 d->delegates.clear();
221 Q_EMIT iconDelegateChanged();
226 return d->separatorDelegate;
229void ToolBarLayout::setSeparatorDelegate(
QQmlComponent *newSeparatorDelegate)
231 if (newSeparatorDelegate == d->separatorDelegate) {
235 d->separatorDelegate = newSeparatorDelegate;
236 d->delegates.clear();
238 Q_EMIT separatorDelegateChanged();
243 return d->moreButton;
246void ToolBarLayout::setMoreButton(
QQmlComponent *newMoreButton)
248 if (newMoreButton == d->moreButton) {
252 d->moreButton = newMoreButton;
253 if (d->moreButtonInstance) {
254 d->moreButtonInstance->deleteLater();
255 d->moreButtonInstance =
nullptr;
258 Q_EMIT moreButtonChanged();
266void ToolBarLayout::setSpacing(qreal newSpacing)
268 if (newSpacing == d->spacing) {
272 d->spacing = newSpacing;
284 if (newAlignment == d->alignment) {
288 d->alignment = newAlignment;
290 Q_EMIT alignmentChanged();
295 return d->visibleWidth;
300 return d->moreButtonInstance ? d->moreButtonInstance->width() : 0;
305 return d->layoutDirection;
310 if (newLayoutDirection == d->layoutDirection) {
314 d->layoutDirection = newLayoutDirection;
316 Q_EMIT layoutDirectionChanged();
321 return d->heightMode;
324void ToolBarLayout::setHeightMode(HeightMode newHeightMode)
326 if (newHeightMode == d->heightMode) {
330 d->heightMode = newHeightMode;
332 Q_EMIT heightModeChanged();
337 d->implicitSizeValid =
false;
341void ToolBarLayout::componentComplete()
348void ToolBarLayout::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
350 if (newGeometry != oldGeometry) {
364void ToolBarLayout::updatePolish()
383void ToolBarLayoutPrivate::calculateImplicitSize()
389 if (!fullDelegate || !iconDelegate || !separatorDelegate || !moreButton) {
390 qCWarning(KirigamiLayoutsLog) <<
"ToolBarLayout: Unable to layout, required properties are not set";
394 if (actions.isEmpty()) {
395 q->setImplicitSize(0., 0.);
399 hiddenActions.clear();
400 firstHiddenIndex = -1;
402 sortedDelegates = createDelegates();
404 bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](
const std::pair<QObject *
const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
405 return entry.second->isReady();
407 if (!ready || !moreButtonInstance) {
411 qreal maxHeight = 0.0;
412 qreal maxWidth = 0.0;
417 for (
auto entry : std::as_const(sortedDelegates)) {
418 if (!entry->isActionVisible()) {
423 if (entry->isHidden()) {
425 hiddenActions.append(entry->action());
429 if (entry->isIconOnly()) {
435 maxWidth += entry->width() + spacing;
436 maxHeight = std::max(maxHeight, entry->maxHeight());
442 visibleActionsWidth = 0.0;
444 q->setImplicitWidth(maxWidth);
446 if (maxWidth > q->width() - (hiddenActions.isEmpty() ? 0.0 : moreButtonInstance->width() + spacing)) {
449 qreal layoutWidth = q->width() - (moreButtonInstance->width() + spacing);
454 layoutWidth -= (moreButtonInstance->width() + spacing);
457 for (
int i = 0; i < sortedDelegates.size(); ++i) {
458 auto delegate = sortedDelegates.at(i);
460 maybeHideDelegate(i, visibleActionsWidth, layoutWidth);
462 if (delegate->isVisible()) {
463 visibleActionsWidth += delegate->width() + spacing;
466 if (!qFuzzyIsNull(visibleActionsWidth)) {
468 visibleActionsWidth -= spacing;
471 visibleActionsWidth = maxWidth;
474 if (!hiddenActions.isEmpty()) {
475 maxHeight = std::max(maxHeight, moreButtonInstance->implicitHeight());
478 q->setImplicitHeight(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);
519 moreButtonInstance->setHeight(height);
521 if (moreButtonInstance->implicitHeight() > height) {
522 moreButtonInstance->setHeight(height);
524 moreButtonInstance->resetHeight();
527 moreButtonInstance->resetHeight();
530 moreButtonInstance->setY(qRound((height - moreButtonInstance->height()) / 2.0));
531 shouldShowMoreButton =
true;
532 moreButtonInstance->setVisible(
true);
534 shouldShowMoreButton =
false;
535 moreButtonInstance->setVisible(
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;
570 if (moreButtonInstance->isVisible()) {
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();
591 QList<ToolBarLayoutDelegate *> result;
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());
611 moreButtonInstance->setVisible(
false);
614 moreButtonInstance->setVisible(shouldShowMoreButton);
618 Q_EMIT q->minimumWidthChanged();
621 delete moreButtonIncubator;
622 moreButtonIncubator =
nullptr;
625 moreButtonIncubator->create();
631ToolBarLayoutDelegate *ToolBarLayoutPrivate::createDelegate(
QObject *action)
633 QQmlComponent *fullComponent =
nullptr;
634 auto displayComponent = action->property(
"displayComponent");
635 if (displayComponent.isValid()) {
636 fullComponent = displayComponent.value<QQmlComponent *>();
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);
655 if (!q->childItems().isEmpty() && q->childItems().first() != newItem) {
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;
678 return layoutDirection ==
Qt::LeftToRight ? offset : q->width() - offset;
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;
773void ToolBarLayoutPrivate::appendAction(ToolBarLayout::ActionsProperty *list,
QObject *action)
775 auto layout =
reinterpret_cast<ToolBarLayout *
>(
list->
data);
776 layout->addAction(action);
779qsizetype ToolBarLayoutPrivate::actionCount(ToolBarLayout::ActionsProperty *list)
781 return reinterpret_cast<ToolBarLayout *
>(
list->
data)->d->actions.count();
784QObject *ToolBarLayoutPrivate::action(ToolBarLayout::ActionsProperty *list, qsizetype index)
786 return reinterpret_cast<ToolBarLayout *
>(
list->
data)->d->actions.at(index);
789void ToolBarLayoutPrivate::clearActions(ToolBarLayout::ActionsProperty *list)
791 reinterpret_cast<ToolBarLayout *
>(
list->
data)->clearActions();
794#include "moc_toolbarlayout.cpp"
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
QObject * object() 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)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setInterval(int msec)