diff --git a/kirigami.qrc b/kirigami.qrc --- a/kirigami.qrc +++ b/kirigami.qrc @@ -18,6 +18,8 @@ src/controls/InlineMessage.qml src/controls/ToolBarApplicationHeader.qml src/controls/private/PrivateActionToolButton.qml + src/controls/private/GlobalDrawerActionItem.qml + src/controls/private/ContextDrawerActionItem.qml src/controls/private/RefreshableScrollView.qml src/controls/private/SwipeItemEventFilter.qml src/controls/private/PageActionPropertyGroup.qml diff --git a/kirigami.qrc.in b/kirigami.qrc.in --- a/kirigami.qrc.in +++ b/kirigami.qrc.in @@ -18,6 +18,8 @@ @kirigami_QML_DIR@/src/controls/InlineMessage.qml @kirigami_QML_DIR@/src/controls/ToolBarApplicationHeader.qml @kirigami_QML_DIR@/src/controls/private/PrivateActionToolButton.qml + @kirigami_QML_DIR@/src/controls/private/GlobalDrawerActionItem.qml + @kirigami_QML_DIR@/src/controls/private/ContextDrawerActionItem.qml @kirigami_QML_DIR@/src/controls/private/RefreshableScrollView.qml @kirigami_QML_DIR@/src/controls/private/SwipeItemEventFilter.qml @kirigami_QML_DIR@/src/controls/private/PageActionPropertyGroup.qml diff --git a/src/controls/Action.qml b/src/controls/Action.qml --- a/src/controls/Action.qml +++ b/src/controls/Action.qml @@ -147,8 +147,22 @@ */ property bool separator: false + /** + * expandible: bool + * When true, actions in globalDrawers and contextDrawers will become titles displaying te child actions as sub items + * @since 2.6 + */ + property bool expandible: false + + property QtObject parent + default property alias children: root.__children property list __children + onChildrenChanged: { + for (var i in children) { + children[i].parent = root + } + } property Shortcut __shortcut: Shortcut { property bool checked: false id: shortcutItem diff --git a/src/controls/BasicListItem.qml b/src/controls/BasicListItem.qml --- a/src/controls/BasicListItem.qml +++ b/src/controls/BasicListItem.qml @@ -84,16 +84,18 @@ Layout.minimumHeight: size Layout.maximumHeight: size Layout.minimumWidth: size - selected: layout.indicateActiveFocus && (listItem.highlighted || listItem.checked || listItem.pressed) + selected: layout.indicateActiveFocus && (listItem.highlighted || listItem.checked || (listItem.pressed && listItem.supportsMouseEvents)) color: listItem.icon && listItem.icon.color && listItem.icon.color.a > 0 ? listItem.icon.color : (selected ? Theme.highlightedTextColor : Theme.textColor) + opacity: 1 } QQC2.Label { id: labelItem text: listItem.text Layout.fillWidth: true - color: layout.indicateActiveFocus && (listItem.highlighted || listItem.checked || listItem.pressed) ? listItem.activeTextColor : listItem.textColor + color: layout.indicateActiveFocus && (listItem.highlighted || listItem.checked || (listItem.pressed && listItem.supportsMouseEvents)) ? listItem.activeTextColor : listItem.textColor elide: Text.ElideRight font: listItem.font + opacity: 1 } } } diff --git a/src/controls/ContextDrawer.qml b/src/controls/ContextDrawer.qml --- a/src/controls/ContextDrawer.qml +++ b/src/controls/ContextDrawer.qml @@ -21,6 +21,7 @@ import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 +import "private" import "templates/private" /** @@ -133,39 +134,17 @@ text: root.title } } - delegate: BasicListItem { - id: listItem - - readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator - - checked: modelData.checked - icon: modelData.icon - supportsMouseEvents: true - separatorVisible: false - reserveSpaceForIcon: !isSeparator - reserveSpaceForLabel: !isSeparator - - label: model ? (model.tooltip ? model.tooltip : model.text) : (modelData.tooltip ? modelData.tooltip : modelData.text) - enabled: !isSeparator && (model ? model.enabled : modelData.enabled) - visible: model ? model.visible : modelData.visible - opacity: enabled ? 1.0 : 0.6 - - Separator { - id: separatorAction - - visible: listItem.isSeparator - Layout.fillWidth: true + delegate: Column { + width: parent.width + ContextDrawerActionItem { + width: parent.width } - - onClicked: { - root.drawerOpen = false; - if (modelData && modelData.trigger !== undefined) { - modelData.trigger(); - // assume the model is a list of QAction or Action - } else if (menu.model.length > index) { - menu.model[index].trigger(); - } else { - console.warning("Don't know how to trigger the action") + Repeater { + model: modelData.hasOwnProperty("expandible") && modelData.expandible ? modelData.children : null + delegate: ContextDrawerActionItem { + width: parent.width + leftPadding: Units.largeSpacing * 2 + opacity: !root.collapsed } } } diff --git a/src/controls/GlobalDrawer.qml b/src/controls/GlobalDrawer.qml --- a/src/controls/GlobalDrawer.qml +++ b/src/controls/GlobalDrawer.qml @@ -127,7 +127,6 @@ */ property list actions - /** * content: list default property * Any random Item can be instantiated inside the drawer and @@ -242,6 +241,7 @@ id: mainFlickable contentWidth: width contentHeight: mainColumn.Layout.minimumHeight + ColumnLayout { id: mainColumn width: mainFlickable.width @@ -455,122 +455,20 @@ Repeater { id: actionsRepeater - model: actions - delegate: - BasicListItem { - id: listItem - supportsMouseEvents: true - readonly property bool wideMode: width > height * 2 - readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator - reserveSpaceForIcon: !isSeparator - reserveSpaceForLabel: !isSeparator - checked: modelData.checked || (actionsMenu && actionsMenu.visible) + model: root.actions + delegate: Column { width: parent.width - - icon: modelData.iconName - - label: width > height * 2 ? MnemonicData.richTextLabel : "" - - MnemonicData.enabled: listItem.enabled && listItem.visible - MnemonicData.controlType: MnemonicData.MenuItem - MnemonicData.label: modelData.text - property ActionsMenu actionsMenu: ActionsMenu { - x: Qt.application.layoutDirection == Qt.RightToLeft ? -width : listItem.width - actions: modelData.children - submenuComponent: Component { - ActionsMenu {} - } - onVisibleChanged: { - if (visible) { - stackView.openSubMenu = listItem.actionsMenu; - } else if (stackView.openSubMenu == listItem.actionsMenu) { - stackView.openSubMenu = null; - } - } - } - - separatorVisible: false - //TODO: animate the hide by collapse - visible: (model ? model.visible || model.visible===undefined : modelData.visible) && opacity > 0 - opacity: (!root.collapsed || icon.length > 0) - Behavior on opacity { - OpacityAnimator { - duration: Units.longDuration/2 - easing.type: Easing.InOutQuad - } - } - enabled: !isSeparator && ( (model && model.enabled !== undefined) ? model.enabled : modelData.enabled) - opacity: enabled ? 1.0 : 0.3 - - Separator { - id: separatorAction - - visible: listItem.isSeparator - Layout.fillWidth: true - } - - Icon { - Shortcut { - sequence: listItem.MnemonicData.sequence - onActivated: listItem.clicked() - } - isMask: true - Layout.alignment: Qt.AlignVCenter - Layout.rightMargin: !Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Units.gridUnit : 0 - Layout.leftMargin: !root.collapsed ? 0 : parent.width - listItem.width - Layout.preferredHeight: !root.collapsed ? Units.iconSizes.smallMedium : Units.iconSizes.small/2 - selected: listItem.checked || listItem.pressed - Layout.preferredWidth: Layout.preferredHeight - source: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic") - visible: !listItem.isSeparator && modelData.children!==undefined && modelData.children.length > 0 - } - data: [ - QQC2.ToolTip { - visible: !listItem.isSeparator && (modelData.tooltip.length || root.collapsed) && (!actionsMenu || !actionsMenu.visible) && listItem.hovered && text.length > 0 - text: modelData.tooltip.length ? modelData.tooltip : modelData.text - delay: 1000 - timeout: 5000 - y: listItem.height/2 - height/2 - x: Qt.application.layoutDirection == Qt.RightToLeft ? -width : listItem.width - } - ] - - onHoveredChanged: { - if (!hovered) { - return; - } - if (stackView.openSubMenu) { - stackView.openSubMenu.visible = false; - - if (!listItem.actionsMenu.hasOwnProperty("count") || listItem.actionsMenu.count>0) { - if (listItem.actionsMenu.hasOwnProperty("popup")) { - listItem.actionsMenu.popup(listItem, listItem.width, 0) - } else { - listItem.actionsMenu.visible = true; - } - } - } + GlobalDrawerActionItem { + id: drawerItem + width: parent.width } - onClicked: { - modelData.trigger(); - if (modelData.children!==undefined && modelData.children.length > 0) { - if (root.collapsed) { - //fallbacks needed for Qt 5.9 - if ((!listItem.actionsMenu.hasOwnProperty("count") || listItem.actionsMenu.count>0) && !listItem.actionsMenu.visible) { - stackView.openSubMenu = listItem.actionsMenu; - if (listItem.actionsMenu.hasOwnProperty("popup")) { - listItem.actionsMenu.popup(listItem, listItem.width, 0) - } else { - listItem.actionsMenu.visible = true; - } - } - } else { - stackView.push(menuComponent, {model: modelData.children, level: level + 1, current: modelData }); - } - } else if (root.resetMenuOnTriggered) { - root.resetMenu(); + Repeater { + model: drawerItem.visible && modelData.hasOwnProperty("expandible") && modelData.expandible ? modelData.children : null + delegate: GlobalDrawerActionItem { + width: parent.width + leftPadding: Units.largeSpacing * 2 + opacity: !root.collapsed } - checked = Qt.binding(function() { return modelData.checked || (actionsMenu && actionsMenu.visible) }); } } } diff --git a/src/controls/private/ContextDrawerActionItem.qml b/src/controls/private/ContextDrawerActionItem.qml new file mode 100644 --- /dev/null +++ b/src/controls/private/ContextDrawerActionItem.qml @@ -0,0 +1,94 @@ +/* + * Copyright 2019 Marco Martin + * + * 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.6 +import QtQuick.Controls 2.0 as QQC2 +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.5 + +BasicListItem { + id: listItem + + readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator + + readonly property bool isExpandible: modelData && modelData.hasOwnProperty("expandible") && modelData.expandible + + checked: modelData.checked + icon: modelData.icon + separatorVisible: false + reserveSpaceForIcon: !isSeparator + reserveSpaceForLabel: !isSeparator + + label: model ? (model.tooltip ? model.tooltip : model.text) : (modelData.tooltip ? modelData.tooltip : modelData.text) + hoverEnabled: (!isExpandible || root.collapsed) && !Settings.tabletMode + sectionDelegate: isExpandible + font.pointSize: isExpandible ? Theme.defaultFont.pointSize * 1.30 : Theme.defaultFont.pointSize + + enabled: !isExpandible && !isSeparator && (model ? model.enabled : modelData.enabled) + visible: model ? model.visible : modelData.visible + opacity: enabled || isExpandible ? 1.0 : 0.6 + + Separator { + id: separatorAction + + visible: listItem.isSeparator + Layout.fillWidth: true + + ActionsMenu { + id: actionsMenu + y: Settings.isMobile ? -height : listItem.height + z: 9999 + actions: modelData.children + submenuComponent: Component { + ActionsMenu {} + } + } + } + + Icon { + isMask: true + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: !Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Units.gridUnit : 0 + Layout.preferredHeight: Units.iconSizes.small/2 + selected: listItem.checked || listItem.pressed + Layout.preferredWidth: Layout.preferredHeight + source: "go-up-symbolic" + visible: !isExpandible && !listItem.isSeparator && modelData.children!== undefined && modelData.children.length > 0 + } + + onPressed: { + if (modelData.children.length > 0) { + actionsMenu.open(); + } + } + onClicked: { + if (modelData.children.length == 0) { + root.drawerOpen = false; + } + + if (modelData && modelData.trigger !== undefined) { + modelData.trigger(); + // assume the model is a list of QAction or Action + } else if (menu.model.length > index) { + menu.model[index].trigger(); + } else { + console.warning("Don't know how to trigger the action") + } + } +} diff --git a/src/controls/private/GlobalDrawerActionItem.qml b/src/controls/private/GlobalDrawerActionItem.qml new file mode 100644 --- /dev/null +++ b/src/controls/private/GlobalDrawerActionItem.qml @@ -0,0 +1,150 @@ +/* + * Copyright 2015 Marco Martin + * + * 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.6 +import QtQuick.Controls 2.0 as QQC2 +import QtQuick.Layouts 1.2 +import org.kde.kirigami 2.5 + +BasicListItem { + id: listItem + supportsMouseEvents: (!isExpandible || root.collapsed) + readonly property bool wideMode: width > height * 2 + readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator + + readonly property bool isExpandible: modelData && modelData.hasOwnProperty("expandible") && modelData.expandible + + reserveSpaceForIcon: !isSeparator + reserveSpaceForLabel: !isSeparator + checked: modelData.checked || (actionsMenu && actionsMenu.visible) + width: parent.width + + icon: modelData.iconName + + label: width > height * 2 ? MnemonicData.richTextLabel : "" + + MnemonicData.enabled: listItem.enabled && listItem.visible + MnemonicData.controlType: MnemonicData.MenuItem + MnemonicData.label: modelData.text + property ActionsMenu actionsMenu: ActionsMenu { + x: Qt.application.layoutDirection == Qt.RightToLeft ? -width : listItem.width + actions: modelData.children + submenuComponent: Component { + ActionsMenu {} + } + onVisibleChanged: { + if (visible) { + stackView.openSubMenu = listItem.actionsMenu; + } else if (stackView.openSubMenu == listItem.actionsMenu) { + stackView.openSubMenu = null; + } + } + } + + separatorVisible: false + //TODO: animate the hide by collapse + visible: (model ? model.visible || model.visible===undefined : modelData.visible) && opacity > 0 + opacity: !root.collapsed || icon.length > 0 + Behavior on opacity { + OpacityAnimator { + duration: Units.longDuration/2 + easing.type: Easing.InOutQuad + } + } + enabled: !isSeparator && ( (model && model.enabled != undefined) ? model.enabled : modelData.enabled) + + hoverEnabled: (!isExpandible || root.collapsed) && !Settings.tabletMode + sectionDelegate: isExpandible + font.pointSize: isExpandible ? Theme.defaultFont.pointSize * 1.30 : Theme.defaultFont.pointSize + + + Separator { + id: separatorAction + + visible: listItem.isSeparator + Layout.fillWidth: true + } + + Icon { + Shortcut { + sequence: listItem.MnemonicData.sequence + onActivated: listItem.clicked() + } + isMask: true + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: !Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Units.gridUnit : 0 + Layout.leftMargin: !root.collapsed ? 0 : parent.width - listItem.width + Layout.preferredHeight: !root.collapsed ? Units.iconSizes.smallMedium : Units.iconSizes.small/2 + selected: listItem.checked || listItem.pressed + Layout.preferredWidth: Layout.preferredHeight + source: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic") + visible: (!isExpandible || root.collapsed) && !listItem.isSeparator && modelData.children!==undefined && modelData.children.length > 0 + } + data: [ + QQC2.ToolTip { + visible: !listItem.isSeparator && (modelData.tooltip.length || root.collapsed) && (!actionsMenu || !actionsMenu.visible) && listItem.hovered && text.length > 0 + text: modelData.tooltip.length ? modelData.tooltip : modelData.text + delay: 1000 + timeout: 5000 + y: listItem.height/2 - height/2 + x: Qt.application.layoutDirection == Qt.RightToLeft ? -width : listItem.width + } + ] + + onHoveredChanged: { + if (!hovered) { + return; + } + if (stackView.openSubMenu) { + stackView.openSubMenu.visible = false; + + if (!listItem.actionsMenu.hasOwnProperty("count") || listItem.actionsMenu.count>0) { + if (listItem.actionsMenu.hasOwnProperty("popup")) { + listItem.actionsMenu.popup(listItem, listItem.width, 0) + } else { + listItem.actionsMenu.visible = true; + } + } + } + } + onClicked: { + if (!supportsMouseEvents) { + return; + } + modelData.trigger(); + if (modelData.children!==undefined && modelData.children.length > 0) { + if (root.collapsed) { + //fallbacks needed for Qt 5.9 + if ((!listItem.actionsMenu.hasOwnProperty("count") || listItem.actionsMenu.count>0) && !listItem.actionsMenu.visible) { + stackView.openSubMenu = listItem.actionsMenu; + if (listItem.actionsMenu.hasOwnProperty("popup")) { + listItem.actionsMenu.popup(listItem, listItem.width, 0) + } else { + listItem.actionsMenu.visible = true; + } + } + } else { + stackView.push(menuComponent, {model: modelData.children, level: level + 1, current: modelData }); + } + } else if (root.resetMenuOnTriggered) { + root.resetMenu(); + } + checked = Qt.binding(function() { return modelData.checked || (actionsMenu && actionsMenu.visible) }); + } +} diff --git a/src/controls/templates/OverlayDrawer.qml b/src/controls/templates/OverlayDrawer.qml --- a/src/controls/templates/OverlayDrawer.qml +++ b/src/controls/templates/OverlayDrawer.qml @@ -351,14 +351,20 @@ //BEGIN signal handlers onCollapsedChanged: { + if (Settings.isMobile) { + collapsed = false; + } if (!__internal.completed) { return; } if ((!collapsible || modal) && collapsed) { collapsed = true; } } onCollapsibleChanged: { + if (Settings.isMobile) { + collapsible = false; + } if (!__internal.completed) { return; }