diff --git a/src/controls/Action.qml b/src/controls/Action.qml --- a/src/controls/Action.qml +++ b/src/controls/Action.qml @@ -29,6 +29,31 @@ Controls.Action { id: root + /** + * Hints for implementations using Actions indicating preferences about how to display the action. + */ + enum DisplayHint { + /** + * Only display an icon for this Action. + */ + IconOnly = 1, + /** + * Try to keep the action visible even when space constrained. + * Mutually exclusive with AlwaysHide, KeepVisible has priority. + */ + KeepVisible = 2, + /** + * If possible, hide the action in an overflow menu or similar location. + * Mutually exclusive with KeepVisible, KeepVisible has priority. + */ + AlwaysHide = 4, + /** + * When this action has children, do not display any indicator (like a + * menu arrow) for this action. + */ + HideChildIndicator = 8 + } + /** * visible: bool * True (default) when the graphic representation of the action @@ -86,6 +111,37 @@ property Controls.Action parent + /** + * displayHint: int + * + * A combination of values from the Action.DisplayHint enum. These are provided to implementations to indicate + * a preference for certain display styles. + * + * Note that these are only preferences, implementations may choose to disregard them. + * + * @since 2.12 + */ + property int displayHint: 0 + + /** + * Helper function to check if a certain display hint has been set. + * + * This function is mostly convenience to enforce the mutual exclusivity of KeepVisible and AlwaysHide. + * + * @param hint The display hint to check if it is set. + * + * @return true if the hint was set for this action, false if not. + * + * @since 2.12 + */ + function displayHintSet(hint) { + if (hint === Action.DisplayHint.AlwaysHide && (displayHint & Action.DisplayHint.KeepVisible)) { + return false; + } + + return (displayHint & hint) + } + default property alias children: root.__children property list __children 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) + } + } + } +} diff --git a/src/controls/private/PrivateActionToolButton.qml b/src/controls/private/PrivateActionToolButton.qml --- a/src/controls/private/PrivateActionToolButton.qml +++ b/src/controls/private/PrivateActionToolButton.qml @@ -42,8 +42,10 @@ //TODO: replace with upstream action when we depend on Qt 5.10 //TODO: upstream action makes the style to re-draw the content, it'd be ideal except for the custom dropDown icon needed for actionsMenu property Controls.Action kirigamiAction - property bool showText: true - property bool showMenuArrow: true + property bool showText: !(kirigamiAction && kirigamiAction.displayHint !== undefined + && kirigamiAction.displayHintSet(Action.DisplayHint.IconOnly)) + property bool showMenuArrow: !(kirigamiAction && kirigamiAction.displayHint !== undefined + && kirigamiAction.displayHintSet(Action.DisplayHint.HideChildIndicator)) property alias menu: menu //we need our own text delegate