diff --git a/components/controls/ButtonBase.qml b/components/controls/ButtonBase.qml index 43d2156c..4542cbd7 100644 --- a/components/controls/ButtonBase.qml +++ b/components/controls/ButtonBase.qml @@ -21,6 +21,7 @@ StyledRect { property bool radiusMorph property alias shapeMorph: stateLayer.shapeMorph + property bool fillWidth // For ButtonRow property font font property int type: ButtonBase.Filled @@ -42,6 +43,7 @@ StyledRect { property color disabledOnColour: Qt.alpha(Colours.palette.m3onSurface, 0.38) property bool internalChecked + property real shapeMorphExpansion: pressed ? 1.16 : 1 readonly property color onColour: disabled ? disabledOnColour : internalChecked ? activeOnColour : inactiveOnColour signal clicked @@ -82,4 +84,10 @@ StyledRect { type: Anim.DefaultEffects } } + + Behavior on shapeMorphExpansion { + Anim { + type: Anim.FastSpatial + } + } } diff --git a/plugin/src/Caelestia/Components/CMakeLists.txt b/plugin/src/Caelestia/Components/CMakeLists.txt index 2928f27d..ac8ce166 100644 --- a/plugin/src/Caelestia/Components/CMakeLists.txt +++ b/plugin/src/Caelestia/Components/CMakeLists.txt @@ -1,8 +1,9 @@ qml_module(caelestia-components URI Caelestia.Components SOURCES - lazylistview.hpp lazylistview.cpp - wavyline.hpp wavyline.cpp + lazylistview.cpp + wavyline.cpp + buttonrow.cpp LIBRARIES Qt::Quick ) diff --git a/plugin/src/Caelestia/Components/buttonrow.cpp b/plugin/src/Caelestia/Components/buttonrow.cpp new file mode 100644 index 00000000..095ffe6f --- /dev/null +++ b/plugin/src/Caelestia/Components/buttonrow.cpp @@ -0,0 +1,127 @@ +#include "buttonrow.hpp" + +namespace caelestia::components { + +ButtonRow::ButtonRow(QQuickItem* parent) + : QQuickItem(parent) + , m_dirty(false) + , m_spacing(0.0) { + setFlag(QQuickItem::ItemHasContents, true); + QObject::connect(this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate); +} + +qreal ButtonRow::spacing() const { + return m_spacing; +} + +void ButtonRow::setSpacing(qreal spacing) { + if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0)) + return; + + m_spacing = spacing; + emit spacingChanged(); + + invalidate(); +} + +void ButtonRow::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) { + if (change == QQuickItem::ItemChildAddedChange) { + auto* const child = data.item; + QObject::connect(child, &QQuickItem::implicitWidthChanged, this, &ButtonRow::invalidate); + QObject::connect(child, &QQuickItem::implicitHeightChanged, this, &ButtonRow::invalidate); + QObject::connect(child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate); + + const auto* childMeta = child->metaObject(); + const auto morphSignalIdx = childMeta->indexOfSignal("shapeMorphExpansionChanged()"); + if (morphSignalIdx != -1) + QObject::connect(child, childMeta->method(morphSignalIdx), this, + metaObject()->method(metaObject()->indexOfSlot("invalidate()"))); + + invalidate(); + } else if (change == QQuickItem::ItemChildRemovedChange) { + QObject::disconnect(data.item, nullptr, this, nullptr); + invalidate(); + } + + QQuickItem::itemChange(change, data); +} + +void ButtonRow::updatePolish() { + if (m_dirty) { + relayout(); + m_dirty = false; + } +} + +void ButtonRow::invalidate() { + m_dirty = true; + polish(); +} + +void ButtonRow::relayout() { + const auto children = childItems(); + const auto nChildren = children.size(); + const auto totalSpacing = static_cast(nChildren - 1) * m_spacing; + + qreal reservedWidth = 0; + int fillWidthCount = 0; + qreal maxHeight = 0; + for (auto* const child : children) { + if (!child->isVisible()) + continue; + + maxHeight = qMax(maxHeight, child->implicitHeight()); + + const auto prop = child->property("fillWidth"); + if (!prop.isValid()) + continue; + + if (prop.toBool()) + fillWidthCount++; + else + reservedWidth += child->implicitWidth(); + } + + if (fillWidthCount == 0) + fillWidthCount = 1; // Avoid divide by 0 + + qreal accX = 0; + const auto widthPerItem = (width() - totalSpacing - reservedWidth) / static_cast(fillWidthCount); + for (int i = 0; i < nChildren; ++i) { + auto* const child = children[i]; + if (!child->isVisible()) + continue; + + auto prevExtraWidth = i > 0 ? getMorphExpansion(children[i - 1]) : 0.0; + auto nextExtraWidth = i < nChildren - 1 ? getMorphExpansion(children[i + 1]) : 0.0; + + // Items at edges push by full amount, items in middle push by half + if (i > 1) + prevExtraWidth /= 2; + if (i < nChildren - 2) + nextExtraWidth /= 2; + + const auto childWidth = child->property("fillWidth").toBool() ? widthPerItem : child->implicitWidth(); + + child->setWidth(childWidth + getMorphExpansion(child, childWidth) - prevExtraWidth - nextExtraWidth); + child->setHeight(maxHeight); + + child->setX(accX); + child->setY(0); + accX += child->width() + m_spacing; + } + + setImplicitWidth(reservedWidth + totalSpacing); + setImplicitHeight(maxHeight); +} + +qreal ButtonRow::getMorphExpansion(const QQuickItem* item) { + return getMorphExpansion(item, item->implicitWidth()); +} + +qreal ButtonRow::getMorphExpansion(const QQuickItem* item, qreal width) { + const auto prop = item->property("shapeMorphExpansion"); + return width * (prop.isValid() ? prop.toReal() - 1 : 0.0); +} + +} // namespace caelestia::components diff --git a/plugin/src/Caelestia/Components/buttonrow.hpp b/plugin/src/Caelestia/Components/buttonrow.hpp new file mode 100644 index 00000000..7a467bc3 --- /dev/null +++ b/plugin/src/Caelestia/Components/buttonrow.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include + +namespace caelestia::components { + +class ButtonRow : public QQuickItem { + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + +public: + explicit ButtonRow(QQuickItem* parent = nullptr); + + qreal spacing() const; + void setSpacing(qreal spacing); + +signals: + void spacingChanged(); + +protected: + void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) override; + void updatePolish() override; + +private slots: + void invalidate(); + +private: + void relayout(); + static qreal getMorphExpansion(const QQuickItem* item); + static qreal getMorphExpansion(const QQuickItem* item, qreal width); + + bool m_dirty; + qreal m_spacing; +}; + +} // namespace caelestia::components