diff --git a/src/controls/ContextDrawer.qml b/src/controls/ContextDrawer.qml index 0ef1b427..69af92c1 100644 --- a/src/controls/ContextDrawer.qml +++ b/src/controls/ContextDrawer.qml @@ -1,183 +1,184 @@ /* * 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.1 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.4 import "private" import "templates/private" /** * A drawer specialization that will show a list of actions that are * specific of the current page shown by the application * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * contextDrawer: Kirigami.ContextDrawer { * id: contextDrawer * } * [...] * } * @endcode * * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.Page { * [...] * contextualActions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * [...] * } * @endcode * * @inherit AbstractDrawer */ OverlayDrawer { id: root - + handleClosedIcon.source: null + handleOpenIcon.source: null /** * title: string * A title for the action list that will be shown to the user when opens the drawer */ property string title: qsTr("Actions") /** * actions: list * This can be any type of object that a ListView can accept as model. * It expects items compatible with either QAction or Kirigami Action */ property var actions: page ? page.contextualActions : [] property Page page: { if (applicationWindow().pageStack.layers && applicationWindow().pageStack.layers.depth > 1 && applicationWindow().pageStack.layers.currentItem.hasOwnProperty("contextualActions")) { return applicationWindow().pageStack.layers.currentItem; } else if (applicationWindow().pageStack.currentItem.hasOwnProperty("contextualActions")) { return applicationWindow().pageStack.currentItem; } else { return applicationWindow().pageStack.lastVisibleItem; } } // Disable for empty menus or when we have a global toolbar enabled: menu.count > 0 && (typeof applicationWindow() === "undefined" || !applicationWindow().pageStack.globalToolBar || (applicationWindow().pageStack.lastVisibleItem && applicationWindow().pageStack.lastVisibleItem.globalToolBarStyle !== ApplicationHeaderStyle.ToolBar)) edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge drawerOpen: false /** * header: Component * Arbitrary content that will go on top of the list (by default is the title) * @since 2.7 */ property alias header: menu.header /** * footer: Component * Arbitrary content that will go on top of the list (by default is empty) * @since 2.7 */ property alias footer: menu.footer //list items go to edges, have their own padding leftPadding: 0 rightPadding: 0 bottomPadding: 0 handleVisible: applicationWindow == undefined ? false : applicationWindow().controlsVisible onPeekingChanged: { if (page) { page.contextualActionsAboutToShow(); } } contentItem: ScrollView { //this just to create the attached property Theme.inherit: true implicitWidth: Units.gridUnit * 20 ListView { id: menu interactive: contentHeight > height model: { if (typeof root.actions == "undefined") { return null; } if (root.actions.length === 0) { return null; } else { return root.actions[0].text !== undefined && root.actions[0].trigger !== undefined ? root.actions : root.actions[0]; } } topMargin: root.handle.y > 0 ? menu.height - menu.contentHeight : 0 header: Item { height: heading.height width: menu.width Heading { id: heading anchors { left: parent.left right: parent.right margins: Units.largeSpacing } elide: Text.ElideRight level: 2 text: root.title } } delegate: Column { width: parent.width ContextDrawerActionItem { width: parent.width } 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 index d1d69d18..a278043f 100644 --- a/src/controls/GlobalDrawer.qml +++ b/src/controls/GlobalDrawer.qml @@ -1,481 +1,483 @@ /* * 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.Templates 2.0 as T2 import QtQuick.Controls 2.0 as QQC2 import QtQuick.Layouts 1.2 import QtGraphicalEffects 1.0 import org.kde.kirigami 2.4 import "private" import "templates/private" /** * A drawer specialization intended for the global actions of the application * valid regardless of the application state (think about the menubar * of a desktop application). * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [ * Kirigami.Action { * text: "View" * iconName: "view-list-icons" * Kirigami.Action { * text: "action 1" * } * Kirigami.Action { * text: "action 2" * } * Kirigami.Action { * text: "action 3" * } * }, * Kirigami.Action { * text: "Sync" * iconName: "folder-sync" * } * ] * } * [...] * } * @endcode * */ OverlayDrawer { id: root edge: Qt.application.layoutDirection == Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge - + handleClosedIcon.source: null + handleOpenIcon.source: null + /** * title: string * A title to be displayed on top of the drawer */ property alias title: bannerImage.title /** * icon: var * An icon to be displayed alongside the title. * It can be a QIcon, a fdo-compatible icon name, or any url understood by Image */ property alias titleIcon: bannerImage.titleIcon /** * bannerImageSource: string * An image to be used as background for the title and icon for * a decorative purpose. * It accepts any url format supported by Image */ property alias bannerImageSource: bannerImage.source /** * actions: list * The list of actions can be nested having a tree structure. * A tree depth bigger than 2 is discouraged. * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [ * Kirigami.Action { * text: "View" * iconName: "view-list-icons" * Kirigami.Action { * text: "action 1" * } * Kirigami.Action { * text: "action 2" * } * Kirigami.Action { * text: "action 3" * } * }, * Kirigami.Action { * text: "Sync" * iconName: "folder-sync" * } * ] * } * [...] * } * @endcode */ property list actions /** * content: list default property * Any random Item can be instantiated inside the drawer and * will be displayed underneath the actions list. * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [...] * Button { * text: "Button" * onClicked: //do stuff * } * } * [...] * } * @endcode */ default property alias content: mainContent.data /** * topContent: list default property * Items that will be instantiated inside the drawer and * will be displayed on top of the actions list. * * Example usage: * @code * import org.kde.kirigami 2.4 as Kirigami * * Kirigami.ApplicationWindow { * [...] * globalDrawer: Kirigami.GlobalDrawer { * actions: [...] * topContent: [Button { * text: "Button" * onClicked: //do stuff * }] * } * [...] * } * @endcode */ property alias topContent: topContent.data /** * showContentWhenCollapsed: bool * If true, when the drawer is collapsed as a sidebar, the content items * at the bottom will be hidden (default false). * If you want to keep some items visible and some invisible, set this to * false and control the visibility/opacity of individual items, * binded to the collapsed property * @since 2.5 */ property bool showContentWhenCollapsed: false /** * showTopContentWhenCollapsed: bool * If true, when the drawer is collapsed as a sidebar, the top content items * at the top will be hidden (default false). * If you want to keep some items visible and some invisible, set this to * false and control the visibility/opacity of individual items, * binded to the collapsed property * @since 2.5 */ property bool showTopContentWhenCollapsed: false /** * resetMenuOnTriggered: bool * * On the actions menu, whenever a leaf action is triggered, the menu * will reset to its parent. */ property bool resetMenuOnTriggered: true /** * currentSubMenu: Action * * Points to the action acting as a submenu */ readonly property Action currentSubMenu: stackView.currentItem ? stackView.currentItem.current: null /** * Notifies that the banner has been clicked */ signal bannerClicked() /** * Reverts the menu back to its initial state */ function resetMenu() { stackView.pop(stackView.get(0, T2.StackView.DontLoad)); if (root.modal) { root.drawerOpen = false; } } rightPadding: !Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Units.gridUnit : Units.smallSpacing contentItem: ScrollView { id: scrollView //ensure the attached property exists Theme.inherit: true anchors.fill: parent implicitWidth: Math.min (Units.gridUnit * 20, root.parent.width * 0.8) horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff Flickable { id: mainFlickable contentWidth: width contentHeight: mainColumn.Layout.minimumHeight ColumnLayout { id: mainColumn width: mainFlickable.width spacing: 0 height: Math.max(root.height, Layout.minimumHeight) //TODO: cable visible of bannerimage Item { implicitHeight: root.collapsible ? Math.max(collapseButton.height + Units.smallSpacing, bannerImage.Layout.preferredHeight) : bannerImage.Layout.preferredHeight Layout.fillWidth: true visible: !bannerImage.empty || root.collapsible BannerImage { id: bannerImage anchors.fill: parent opacity: !root.collapsed fillMode: Image.PreserveAspectCrop Behavior on opacity { OpacityAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } leftPadding: root.collapsible ? collapseButton.width + Units.smallSpacing*2 : topPadding MouseArea { anchors.fill: parent onClicked: root.bannerClicked() } EdgeShadow { edge: Qt.BottomEdge visible: bannerImageSource != "" anchors { left: parent.left right: parent.right bottom: parent.top } } } PrivateActionToolButton { id: collapseButton readonly property bool noTitle: (!root.title || root.title.length===0) && (!root.titleIcon || root.title.length===0) anchors { top: parent.top left: parent.left topMargin: root.collapsed || noTitle ? 0 : Units.smallSpacing + Units.iconSizes.large/2 - height/2 leftMargin: root.collapsed || noTitle ? 0 : Units.smallSpacing Behavior on leftMargin { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Behavior on topMargin { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } width: Units.iconSizes.smallMedium + Units.largeSpacing * 2 height: width Behavior on y { YAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } visible: root.collapsible kirigamiAction: Action { icon.name: "application-menu" checkable: true checked: !root.collapsed onCheckedChanged: root.collapsed = !checked } } } ColumnLayout { id: topContent spacing: 0 Layout.alignment: Qt.AlignHCenter Layout.leftMargin: root.leftPadding Layout.rightMargin: root.rightPadding Layout.bottomMargin: Units.smallSpacing Layout.topMargin: root.topPadding Layout.fillWidth: true Layout.fillHeight: true Layout.preferredHeight: implicitHeight * opacity //NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient //as items are added only after this column creation Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding visible: children.length > 0 && childrenRect.height > 0 && opacity > 0 opacity: !root.collapsed || showTopContentWhenCollapsed Behavior on opacity { //not an animator as is binded NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } T2.StackView { id: stackView Layout.fillWidth: true Layout.minimumHeight: currentItem ? currentItem.implicitHeight : 0 Layout.maximumHeight: Layout.minimumHeight property ActionsMenu openSubMenu initialItem: menuComponent //NOTE: it's important those are NumberAnimation and not XAnimators // as while the animation is running the drawer may close, and //the animator would stop when not drawing see BUG 381576 popEnter: Transition { NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } popExit: Transition { NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: 400; easing.type: Easing.OutCubic } } pushEnter: Transition { NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } pushExit: Transition { NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: 400; easing.type: Easing.OutCubic } } replaceEnter: Transition { NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: 400; easing.type: Easing.OutCubic } } replaceExit: Transition { NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: 400; easing.type: Easing.OutCubic } } } Item { Layout.fillWidth: true Layout.fillHeight: root.actions.length>0 Layout.minimumHeight: Units.smallSpacing } ColumnLayout { id: mainContent Layout.alignment: Qt.AlignHCenter Layout.leftMargin: root.leftPadding Layout.rightMargin: root.rightPadding Layout.fillWidth: true Layout.fillHeight: true //NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient //as items are added only after this column creation Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding visible: children.length > 0 && (opacity > 0 || mainContentAnimator.running) opacity: !root.collapsed || showContentWhenCollapsed Behavior on opacity { OpacityAnimator { id: mainContentAnimator duration: Units.longDuration easing.type: Easing.InOutQuad } } } Item { Layout.minimumWidth: Units.smallSpacing Layout.minimumHeight: root.bottomPadding } Component { id: menuComponent Column { spacing: 0 property alias model: actionsRepeater.model property Action current property int level: 0 Layout.maximumHeight: Layout.minimumHeight move: Transition { YAnimator { duration: Units.longDuration/2 easing.type: Easing.InOutQuad } } BasicListItem { id: backItem visible: level > 0 supportsMouseEvents: true icon: (LayoutMirroring.enabled ? "go-previous-symbolic-rtl" : "go-previous-symbolic") label: MnemonicData.richTextLabel MnemonicData.enabled: backItem.enabled && backItem.visible MnemonicData.controlType: MnemonicData.MenuItem MnemonicData.label: qsTr("Back") separatorVisible: false onClicked: stackView.pop() } Shortcut { sequence: backItem.MnemonicData.sequence onActivated: backItem.clicked() } Repeater { id: actionsRepeater model: root.actions delegate: Column { width: parent.width GlobalDrawerActionItem { id: drawerItem width: parent.width } Repeater { model: drawerItem.visible && modelData.hasOwnProperty("expandible") && modelData.expandible ? modelData.children : null delegate: GlobalDrawerActionItem { width: parent.width leftPadding: Units.largeSpacing * 2 opacity: !root.collapsed } } } } } } } } } } diff --git a/src/controls/OverlayDrawer.qml b/src/controls/OverlayDrawer.qml index 0c9cd6a9..92b77103 100644 --- a/src/controls/OverlayDrawer.qml +++ b/src/controls/OverlayDrawer.qml @@ -1,165 +1,162 @@ /* * Copyright 2016 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.1 import QtGraphicalEffects 1.0 import QtQuick.Templates 2.0 as T2 import org.kde.kirigami 2.5 import "private" import "templates" as T /** * Overlay Drawers are used to expose additional UI elements needed for * small secondary tasks for which the main UI elements are not needed. * For example in Okular Active, an Overlay Drawer is used to display * thumbnails of all pages within a document along with a search field. * This is used for the distinct task of navigating to another page. */ T.OverlayDrawer { id: root //BEGIN Properties focus: false modal: true drawerOpen: !modal closePolicy: modal ? T2.Popup.CloseOnEscape | T2.Popup.CloseOnReleaseOutside : T2.Popup.NoAutoClose handleVisible: (modal || !drawerOpen) && (typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true) onPositionChanged: { if (!modal && !root.peeking && !root.animating) { position = 1; } } background: Rectangle { color: Theme.backgroundColor Item { parent: root.handle anchors.fill: parent DropShadow { anchors.fill: handleGraphics visible: !parent.parent.handleAnchor || !parent.parent.handleAnchor.visible || root.handle.pressed || (root.modal && root.position > 0) horizontalOffset: 0 verticalOffset: Units.devicePixelRatio radius: Units.gridUnit /2 samples: 16 color: Qt.rgba(0, 0, 0, root.handle.pressed ? 0.6 : 0.4) source: handleGraphics } Rectangle { id: handleGraphics anchors.centerIn: parent Theme.colorSet: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Theme.colorSet : Theme.Button Theme.backgroundColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Theme.backgroundColor : undefined Theme.textColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Theme.textColor : undefined Theme.inherit: false color: root.handle.pressed ? Theme.highlightColor : Theme.backgroundColor visible: !parent.parent.handleAnchor || !parent.parent.handleAnchor.visible width: Units.iconSizes.smallMedium + Units.smallSpacing * 2 height: width radius: Units.devicePixelRatio * 2 Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } Loader { anchors.centerIn: handleGraphics width: height height: Units.iconSizes.smallMedium Theme.colorSet: handleGraphics.Theme.colorSet Theme.backgroundColor: handleGraphics.Theme.backgroundColor Theme.textColor: handleGraphics.Theme.textColor source: { var edge = root.edge; if (Qt.application.layoutDirection == Qt.RightToLeft) { if (edge === Qt.LeftEdge) { edge = Qt.RightEdge; } else { edge = Qt.LeftEdge; } } - switch(edge) { - case Qt.LeftEdge: - return Qt.resolvedUrl("templates/private/MenuIcon.qml"); - case Qt.RightEdge: { - if (root.hasOwnProperty("actions")) { - return Qt.resolvedUrl("templates/private/ContextIcon.qml"); - } else { - return Qt.resolvedUrl("templates/private/GenericDrawerIcon.qml"); - } - } - default: - return ""; - } + + if (root.handleClosedIcon.source && root.handleOpenIcon.source) { + return Qt.resolvedUrl("templates/private/GenericDrawerIcon.qml"); + } else if (edge == Qt.LeftEdge ) { + return Qt.resolvedUrl("templates/private/MenuIcon.qml"); + } else if(edge == Qt.RightEdge && root.hasOwnProperty("actions")) { + return Qt.resolvedUrl("templates/private/ContextIcon.qml"); + }else { + return ""; + } } onItemChanged: { if(item) { item.drawer = Qt.binding(function(){return root}); item.color = Qt.binding(function(){return root.handle.pressed ? Theme.highlightedTextColor : Theme.textColor}); } } } } Separator { anchors { right: root.edge == Qt.RightEdge ? parent.left : (root.edge == Qt.LeftEdge ? undefined : parent.right) left: root.edge == Qt.LeftEdge ? parent.right : (root.edge == Qt.RightEdge ? undefined : parent.left) top: root.edge == Qt.TopEdge ? parent.bottom : (root.edge == Qt.BottomEdge ? undefined : parent.top) bottom: root.edge == Qt.BottomEdge ? parent.top : (root.edge == Qt.TopEdge ? undefined : parent.bottom) } visible: !root.modal } EdgeShadow { z: -2 visible: root.modal edge: root.edge anchors { right: root.edge == Qt.RightEdge ? parent.left : (root.edge == Qt.LeftEdge ? undefined : parent.right) left: root.edge == Qt.LeftEdge ? parent.right : (root.edge == Qt.RightEdge ? undefined : parent.left) top: root.edge == Qt.TopEdge ? parent.bottom : (root.edge == Qt.BottomEdge ? undefined : parent.top) bottom: root.edge == Qt.BottomEdge ? parent.top : (root.edge == Qt.TopEdge ? undefined : parent.bottom) } opacity: root.position == 0 ? 0 : 1 Behavior on opacity { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } } }