diff --git a/kirigami.qrc b/kirigami.qrc --- a/kirigami.qrc +++ b/kirigami.qrc @@ -32,6 +32,7 @@ src/controls/private/DefaultCardBackground.qml src/controls/private/BannerImage.qml src/controls/private/EdgeShadow.qml + src/controls/private/ActionToolBarLayoutDetails.qml src/controls/private/globaltoolbar/AbstractPageHeader.qml src/controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml src/controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml diff --git a/kirigami.qrc.in b/kirigami.qrc.in --- a/kirigami.qrc.in +++ b/kirigami.qrc.in @@ -33,6 +33,7 @@ @kirigami_QML_DIR@/src/controls/private/DefaultCardBackground.qml @kirigami_QML_DIR@/src/controls/private/BannerImage.qml @kirigami_QML_DIR@/src/controls/private/EdgeShadow.qml + @kirigami_QML_DIR@/src/controls/private/ActionToolBarLayoutDetails.qml @kirigami_QML_DIR@/src/controls/private/globaltoolbar/AbstractPageHeader.qml @kirigami_QML_DIR@/src/controls/private/globaltoolbar/BreadcrumbControl.qml @kirigami_QML_DIR@/src/controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml diff --git a/src/controls/ActionToolBar.qml b/src/controls/ActionToolBar.qml --- a/src/controls/ActionToolBar.qml +++ b/src/controls/ActionToolBar.qml @@ -96,7 +96,7 @@ } Repeater { - model: placeholderLayout.visibleActions + model: root.actions delegate: PrivateActionToolButton { id: actionDelegate @@ -107,8 +107,11 @@ // from creating useless spacing Layout.rightMargin: Kirigami.Units.smallSpacing + visible: details.visibleActions.indexOf(modelData) != -1 + && (modelData.visible === undefined || modelData.visible) + flat: root.flat && !modelData.icon.color.a - display: root.display + display: details.iconOnlyActions.indexOf(modelData) != -1 ? Controls.Button.IconOnly : root.display kirigamiAction: modelData } } @@ -122,82 +125,36 @@ id: moreButton Layout.alignment: Qt.AlignRight - - visible: hiddenActions.length > 0 || placeholderLayout.hiddenActions.length > 0 - showMenuArrow: false + visible: root.hiddenActions.length > 0 || details.hiddenActions.length > 0 kirigamiAction: Kirigami.Action { icon.name: "overflow-menu" + displayHint: Kirigami.Action.DisplayHint.IconOnly | Kirigami.Action.DisplayHint.HideChildIndicator children: Array.prototype.map.call(root.actions, function (i) { return i }).concat(Array.prototype.map.call(hiddenActions, function (i) { return i })) } menu.submenuComponent: ActionsMenu { Binding { target: parentItem property: "visible" - value: placeholderLayout.visibleActions.indexOf(parentAction) == -1 && + value: details.visibleActions.indexOf(parentAction) == -1 && (parentAction.visible === undefined || parentAction.visible) } } menu.itemDelegate: ActionMenuItem { - visible: placeholderLayout.visibleActions.indexOf(action) == -1 && + visible: details.visibleActions.indexOf(action) == -1 && (action.visible === undefined || action.visible) } } } - RowLayout { - id: placeholderLayout - enabled: false - opacity: 0 // Cannot use visible: false because then relayout doesn't happen correctly - spacing: Kirigami.Units.smallSpacing - - property var visibleActions: [] - property var hiddenActions: [] - property real layoutWidth: root.width - moreButton.width - Kirigami.Units.smallSpacing - - Repeater { - id: placeholderRepeater - model: root.actions - - PrivateActionToolButton { - flat: root.flat && !modelData.icon.color.a - display: root.display - visible: modelData.visible === undefined || modelData.visible - kirigamiAction: modelData - - property bool actionVisible: x + width < placeholderLayout.layoutWidth - } - } - - Component.onCompleted: Qt.callLater(updateVisibleActions) - onWidthChanged: Qt.callLater(updateVisibleActions) - - function updateVisibleActions() { - var visible = [] - var hidden = [] - - if (root.width >= placeholderLayout.width + moreButton.width + Kirigami.Units.smallSpacing) { - visibleActions = Array.prototype.map.call(root.actions, function(item) { return item }) - hiddenActions = [] - return - } - - for (var i = 0; i < root.actions.length; ++i) { - var item = placeholderRepeater.itemAt(i) - if (item.actionVisible) { - visible.push(item.kirigamiAction) - } else { - hidden.push(item.kirigamiAction) - - } - } - - visibleActions = visible - hiddenActions = hidden - } + ActionToolBarLayoutDetails { + id: details + anchors.fill: parent + actions: root.actions + rightPadding: moreButton.width + Kirigami.Units.smallSpacing + flat: root.flat + display: root.display } - - onWidthChanged: Qt.callLater(placeholderLayout.updateVisibleActions) } diff --git a/src/controls/private/ActionToolBarLayoutDetails.qml b/src/controls/private/ActionToolBarLayoutDetails.qml new file mode 100644 --- /dev/null +++ b/src/controls/private/ActionToolBarLayoutDetails.qml @@ -0,0 +1,148 @@ +/* + * Copyright 2018 Marco Martin + * Copyright 2019 Arjen Hiemstra + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Layouts 1.2 +import QtQuick.Controls 2.4 as Controls +import org.kde.kirigami 2.5 as Kirigami + +/** + * This fairly complex thing determines the layout of ActionToolBar, that is, + * which actions should be displayed in full width toolbutton form, which should + * be displayed in an icon-only reduced size and which should be placed in the + * overflow menu. + * + * It makes use of two fairly static layouts, one contains all actions in expanded + * full-size form, the other in reduced icon-only form. The items in these layouts + * determine if they should be visible based on their relative position and size + * and some properties of the action. The update function then goes through these + * items, adding the actions to the relevant lists, so they can be used by the + * ActionToolBar to determine which action should be visible in what state. + * + * The reason for using two separate layouts from ActionToolBar's main layout is + * so that we can use the actual geometry of the buttons to calculate the + * visibility, completely disregarding any other layouting quirks. We can then + * use that information so only the relevant things in the ActionToolBar are + * visible. This allows the main ActionToolBar to use normal layout features for + * things like the positioning of the visible actions. + */ +Item { + id: details + + property var actions + + property var visibleActions: [] + property var hiddenActions: [] + property var iconOnlyActions: [] + + property bool flat: false + property int display: Controls.Button.TextBesideIcon + property real spacing: Kirigami.Units.smallSpacing + property real leftPadding: 0 + property real rightPadding: 0 + + property real iconOnlyWidth: 0 + readonly property real iconLayoutWidth: width - rightPadding + readonly property real fullLayoutWidth: iconLayoutWidth - iconOnlyWidth + + enabled: false + opacity: 0 // Cannot use visible: false because then relayout doesn't happen correctly + + function update() { + var visible = [] + var hidden = [] + var iconOnly = [] + var iconsWidth = 0 + + if (details.width >= fullSizePlaceholderLayout.width + details.rightPadding) { + visibleActions = Array.prototype.map.call(details.actions, function(i) { return i }) + hiddenActions = [] + iconOnlyActions = [] + iconOnlyWidth = 0 + return + } + + for (var i = 0; i < root.actions.length; ++i) { + var item = fullSizePlaceholderRepeater.itemAt(i) + var iconOnlyItem = iconOnlyPlaceholderRepeater.itemAt(i) + + if (item.actionVisible) { + visible.push(item.kirigamiAction) + } else if (iconOnlyItem.actionVisible) { + visible.push(item.kirigamiAction) + iconOnly.push(item.kirigamiAction) + iconsWidth += iconOnlyItem.width + details.spacing + } else { + hidden.push(item.kirigamiAction) + } + } + + visibleActions = visible + hiddenActions = hidden + iconOnlyActions = iconOnly + iconOnlyWidth = iconsWidth + } + + onWidthChanged: Qt.callLater(update) + Component.onCompleted: Qt.callLater(update) + + RowLayout { + id: fullSizePlaceholderLayout + spacing: details.spacing + + // This binding is here to take care of things like visibility changes + onWidthChanged: Qt.callLater(details.update) + + Repeater { + id: fullSizePlaceholderRepeater + + model: details.actions + + PrivateActionToolButton { + flat: details.flat && !modelData.icon.color.a + display: details.display + visible: (modelData.visible === undefined || modelData.visible) + && (modelData.displayHint !== undefined && !modelData.displayHintSet(Kirigami.Action.DisplayHint.AlwaysHide)) + kirigamiAction: modelData + property bool actionVisible: visible && (x + width < details.fullLayoutWidth) + } + } + } + + RowLayout { + id: iconOnlyPlaceholderLayout + spacing: details.spacing + + Repeater { + id: iconOnlyPlaceholderRepeater + + model: details.actions + + PrivateActionToolButton { + flat: details.flat && !modelData.icon.color.a + display: Controls.Button.IconOnly + visible: (modelData.visible === undefined || modelData.visible) + && (modelData.displayHint !== undefined && modelData.displayHintSet(Kirigami.Action.DisplayHint.KeepVisible)) + kirigamiAction: modelData + property bool actionVisible: visible && (x + width < details.iconLayoutWidth) + } + } + } +}