diff --git a/src/controls/Action.qml b/src/controls/Action.qml index 7e18ca29..a803354b 100644 --- a/src/controls/Action.qml +++ b/src/controls/Action.qml @@ -1,170 +1,173 @@ /* * 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.7 import "private" /** * An item that represents an abstract Action * * @inherit QtObject */ QtObject { id: root /** * Emitted whenever a action's checked property changes. * This usually happens at the same time as triggered. * @param checked */ signal toggled(bool checked) /** * Emitted when either the menu item or its bound action have been activated. Includes the object that triggered the event if relevant (e.g. a Button). * You shouldn't need to emit this signal, use trigger() instead. * @param source Object that triggered the event if relevant, often null */ signal triggered(QtObject source) /** * visible: bool * True (default) when the graphic representation of the action * is supposed to be visible. * It's up to the action representation to honor this property. */ property bool visible: true /** * checkable: bool * Whether action can be checked, or toggled. Defaults to false. */ property bool checkable: false /** * checked: bool * Whether the action is checked. Defaults to false. */ property bool checked: false /** * enabled: bool * Whether the action is enabled, and can be triggered. Defaults to true. */ property bool enabled: true /** * iconName: string * Sets the icon name for the action. This will pick the icon with the given name from the current theme. */ property alias iconName: iconGroup.name /** * iconSource: string * Sets the icon file or resource url for the action. Defaults to the empty URL. Use this if you want a specific file rather than an icon from the theme */ property alias iconSource: iconGroup.source /** * metadata for the icon, such as width/height.name and source * * name This property holds the name of the icon to use. * The icon will be loaded from the platform theme. * If the icon is found in the theme, it will always be used; * even if icon.source is also set. If the icon is not found, * icon.source will be used instead. * For more information on theme icons, see QIcon::fromTheme(). * * * source This property holds the name of the icon to use. * The icon will be loaded as a regular image. * If icon.name is set and refers to a valid theme icon, * it will always be used instead of this property. * * * width This property holds the width of the icon. * The icon's width will never exceed this value, * though it will shrink when necessary. * height This property holds the height of the icon. * The icon's height will never exceed this value, * though it will shrink when necessary. * * *color This property holds the color of the icon. * The icon is tinted with the specified color, unless the color is set to "transparent". */ property ActionIconGroup icon: ActionIconGroup { id: iconGroup } /** * shortcut : keysequence * Shortcut bound to the action. The keysequence can be a string or a Qt standard key. */ property alias shortcut: shortcutItem.sequence /** * Text for the action. This text will show as the button text, or as title in a menu item, depending from the way the developer will choose to represent it */ property string text /** * A tooltip text to be shown when hovering the control bound to this action. Not all controls support tooltips on all platforms */ property string tooltip /** * children: list * A list of children actions. * Useful for tree-like menus * @code * Action { * text: "Tools" * Action { * text: "Action1" * } * Action { * text: "Action2" * } * } * @endcode */ /** * separator: bool * Whether the action is is a separator action; defaults to false. */ property bool separator: false + + property bool expandible: false + default property alias children: root.__children property list __children property Shortcut __shortcut: Shortcut { property bool checked: false id: shortcutItem enabled: root.enabled onActivated: root.trigger(); } function trigger(source) { if (!enabled) { return; } root.triggered(source); if (root.checkable) { root.checked = !root.checked; root.toggled(root.checked); } } onCheckedChanged: root.toggled(root.checked); } diff --git a/src/controls/GlobalDrawer.qml b/src/controls/GlobalDrawer.qml index 76e8f7b0..9b3ef45e 100644 --- a/src/controls/GlobalDrawer.qml +++ b/src/controls/GlobalDrawer.qml @@ -1,583 +1,597 @@ /* * 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 /** * 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 + + property var expandedActions: { + var expanded = []; + var action; + for (var i in actions) { + action = actions[i]; + expanded.push(action); + if (action.children.length > 0 && action.hasOwnProperty("expandible") && action.expandible) { + expanded.push.apply(expanded, action.children) + } + } + return expanded; + } + 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: actions + model: mainFlickable.expandedActions delegate: BasicListItem { id: listItem supportsMouseEvents: true readonly property bool wideMode: width > height * 2 readonly property bool isSeparator: modelData.hasOwnProperty("separator") && modelData.separator + readonly property bool isExpandible: 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) - opacity: enabled ? 1.0 : 0.3 + enabled: (!isExpandible || root.collapsed) && !isSeparator && ( (model && model.enabled != undefined) ? model.enabled : modelData.enabled) + opacity: (model && model.enabled != undefined) ? model.enabled : modelData.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 + 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: { 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 index 0697eb96..bb8c5976 100644 --- a/src/controls/templates/OverlayDrawer.qml +++ b/src/controls/templates/OverlayDrawer.qml @@ -1,484 +1,490 @@ /* * Copyright 2012 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.Templates 2.0 as T2 import org.kde.kirigami 2.5 import "private" /** * 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. * @inherits: QtQuick.Templates.Drawer */ T2.Drawer { id: root z: modal ? (Math.round((position * 100) / 10) ): 1000000 //BEGIN Properties /** * drawerOpen: bool * true when the drawer is open and visible */ property bool drawerOpen: false /** * enabled: bool * This property holds whether the item receives mouse and keyboard events. By default this is true. */ property bool enabled: true /** * peeking: true * When true the drawer is in a state between open and closed. the drawer is visible but not completely open. * This is usually the case when the user is dragging the drawer from a screen * edge, so the user is "peeking" what's in the drawer */ property bool peeking: false /** * animating: Bool * True during an animation of a drawer either opening or closing */ readonly property bool animating : enterAnimation.animating || exitAnimation.animating || positionResetAnim.running /** * collapsible: Bool * When true, the drawer can be collapsed to a very thin, usually icon only sidebar. * Only modal drawers are collapsible. * Collapsible is not supported in Mobile mode * @since 2.5 */ property bool collapsible: false /** * collapsed: bool * When true, the drawer will be collapsed to a very thin sidebar, * usually icon only. * Only collapsible drawers can be collapsed */ property bool collapsed: false /** * collapsedSize: int * When collapsed, the drawer will be resized to this size * (which may be width for vertical drawers or height for * horizontal drawers). * By default it's just enough to accommodate medium sized icons */ property int collapsedSize: Units.iconSizes.medium /** * A grouped property describing an optional icon. * * source: The source of the icon, a freedesktop-compatible icon name is recommended. * * color: An optional tint color for the icon. * * If no custom icon is set, a menu icon is shown for the application globalDrawer * and an overflow menu icon is shown for the contextDrawer. * That's the default for the GlobalDrawer and ContextDrawer components respectively. * * For OverlayDrawer the default is view-right-close or view-left-close depending on the drawer location * @since 2.5 */ readonly property QtObject handleOpenIcon: IconPropertiesGroup {source: root.edge == Qt.RightEdge ? "view-right-close" : "view-left-close"} /** * A grouped property describing an optional icon. * * source: The source of the icon, a freedesktop-compatible icon name is recommended. * * color: An optional tint color for the icon. * * If no custom icon is set, an X icon is shown, * which will morph into the Menu or overflow icons * * For OverlayDrawer the default is view-right-new or view-left-new depending on the drawer location * @since 2.5 */ readonly property QtObject handleClosedIcon: IconPropertiesGroup {source: root.edge == Qt.RightEdge ? "view-right-new" : "view-left-new"} /** * handleVisible: bool * If true, a little handle will be visible to make opening the drawer easier * Currently supported only on left and right drawers */ property bool handleVisible: typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true /** * handle: Item * Readonly property that points to the item that will act as a physical * handle for the Drawer **/ readonly property Item handle: MouseArea { id: drawerHandle z: root.modal ? applicationWindow().overlay.z + (root.position > 0 ? +1 : -1) : root.background.parent.z + 1 preventStealing: true hoverEnabled: handleAnchor parent: applicationWindow().overlay.parent property Item handleAnchor: (!Settings.isMobile && applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar && applicationWindow().pageStack.globalToolBar.actualStyle != ApplicationHeaderStyle.None) ? (root.edge == Qt.LeftEdge ? applicationWindow().pageStack.globalToolBar.leftHandleAnchor : applicationWindow().pageStack.globalToolBar.rightHandleAnchor) : (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") !== -1 ? applicationWindow().header : null) property int startX property int mappedStartX enabled: root.handleVisible onPressed: { root.peeking = true; startX = mouse.x; mappedStartX = mapToItem(parent, startX, 0).x } onPositionChanged: { if (!pressed) { return; } var pos = mapToItem(parent, mouse.x - startX, mouse.y); switch(root.edge) { case Qt.LeftEdge: root.position = pos.x/root.contentItem.width; break; case Qt.RightEdge: root.position = (root.parent.width - pos.x - width)/root.contentItem.width; break; default: } } onReleased: { root.peeking = false; if (Math.abs(mapToItem(parent, mouse.x, 0).x - mappedStartX) < Qt.styleHints.startDragDistance) { if (!root.drawerOpen) { root.close(); } root.drawerOpen = !root.drawerOpen; } } onCanceled: { root.peeking = false } x: { switch(root.edge) { case Qt.LeftEdge: return root.background.width * root.position + Units.smallSpacing; case Qt.RightEdge: return drawerHandle.parent.width - (root.background.width * root.position) - width - Units.smallSpacing; default: return 0; } } y: handleAnchor && anchors.bottom ? handleAnchor.ScenePosition.y : 0 anchors { bottom: drawerHandle.handleAnchor ? undefined : parent.bottom bottomMargin: { if (typeof applicationWindow === "undefined") { return; } var margin = Units.smallSpacing; if (applicationWindow().footer) { margin = applicationWindow().footer.height + Units.smallSpacing; } if (!applicationWindow() || !applicationWindow().pageStack || !applicationWindow().pageStack.contentItem || !applicationWindow().pageStack.contentItem.itemAt) { return margin; } var item; if (applicationWindow().pageStack.layers.depth > 1) { item = applicationWindow().pageStack.layers.currentItem; } else { item = applicationWindow().pageStack.contentItem.itemAt(applicationWindow().pageStack.contentItem.contentX + drawerHandle.x, 0); } //try to take the last item if (!item) { item = applicationWindow().pageStack.lastItem; } var pageFooter = item && item.page ? item.page.footer : (item ? item.footer : undefined); if (pageFooter) { margin += pageFooter.height; } return margin; } Behavior on bottomMargin { NumberAnimation { duration: Units.shortDuration easing.type: Easing.InOutQuad } } } visible: root.enabled && (root.edge == Qt.LeftEdge || root.edge == Qt.RightEdge) width: handleAnchor ? handleAnchor.width : Units.iconSizes.smallMedium + Units.smallSpacing*2 height: handleAnchor ? handleAnchor.height : width opacity: root.handleVisible ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } transform: Translate { id: translateTransform x: root.handleVisible ? 0 : (root.edge == Qt.LeftEdge ? -drawerHandle.width : drawerHandle.width) Behavior on x { NumberAnimation { duration: Units.longDuration easing.type: !root.handleVisible ? Easing.OutQuad : Easing.InQuad } } } } Theme.colorSet: modal ? Theme.View : Theme.Window Theme.onColorSetChanged: { contentItem.Theme.colorSet = Theme.colorSet background.Theme.colorSet = Theme.colorSet } //END Properties //BEGIN reassign properties //default paddings leftPadding: Units.smallSpacing topPadding: Units.smallSpacing rightPadding: Units.smallSpacing bottomPadding: Units.smallSpacing y: modal ? 0 : ((T2.ApplicationWindow.menuBar ? T2.ApplicationWindow.menuBar.height : 0) + (T2.ApplicationWindow.header ? T2.ApplicationWindow.header.height : 0)) height: modal ? parent.height : (parent.height - y - (T2.ApplicationWindow.footer ? T2.ApplicationWindow.footer.height : 0)) parent: modal || edge === Qt.LeftEdge || edge === Qt.RightEdge ? T2.ApplicationWindow.overlay : T2.ApplicationWindow.contentItem edge: Qt.LeftEdge modal: true dragMargin: enabled && (edge == Qt.LeftEdge || edge == Qt.RightEdge) ? Qt.styleHints.startDragDistance : 0 contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0) contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0) implicitWidth: contentWidth + leftPadding + rightPadding implicitHeight: contentHeight + topPadding + bottomPadding //this is a workaround for the height not being propagated automatically only sometimes // see https://bugs.kde.org/show_bug.cgi?id=398163 //NOTE: this is NOT a binding, otherwise it causes a binding loop in implicitHeight Connections { target: parent onWidthChanged: { if (edge == Qt.TopEdge || edge == Qt.BottomEdge) { width = parent.width; } } onHeightChanged: { if (edge == Qt.LeftEdge || edge == Qt.RightEdge) { height = parent.height; } } } enter: Transition { SequentialAnimation { id: enterAnimation /*NOTE: why this? the running status of the enter transition is not relaible and * the SmoothedAnimation is always marked as non running, * so the only way to get to a reliable animating status is with this */ property bool animating ScriptAction { script: { enterAnimation.animating = true //on non modal dialog we don't want drawers in the overlay if (!root.modal) { root.background.parent.parent = applicationWindow().overlay.parent } } } SmoothedAnimation { velocity: 5 } ScriptAction { script: enterAnimation.animating = false } } } exit: Transition { SequentialAnimation { id: exitAnimation property bool animating ScriptAction { script: exitAnimation.animating = true } SmoothedAnimation { velocity: 5 } ScriptAction { script: exitAnimation.animating = false } } } //END reassign properties //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; } if (!collapsible) { collapsed = false; } else if (modal) { collapsible = false; } } onModalChanged: { if (!__internal.completed) { return; } if (modal) { collapsible = false; } } onPositionChanged: { if (peeking) { visible = true } } onVisibleChanged: { if (peeking) { visible = true } else { drawerOpen = visible; } } onPeekingChanged: { if (peeking) { root.enter.enabled = false; root.exit.enabled = false; } else { drawerOpen = position > 0.5 ? 1 : 0; positionResetAnim.running = true root.enter.enabled = true; root.exit.enabled = true; } } onDrawerOpenChanged: { //sync this property only when the component is properly loaded if (!__internal.completed) { return; } positionResetAnim.running = false; if (drawerOpen) { open(); } else { close(); } } Component.onCompleted: { //if defined as drawerOpen by default in QML, don't animate if (root.drawerOpen) { root.enter.enabled = false; root.visible = true; root.position = 1; root.enter.enabled = true; } __internal.completed = true; contentItem.Theme.colorSet = Theme.colorSet; background.Theme.colorSet = Theme.colorSet; } //END signal handlers //this is as hidden as it can get here property QtObject __internal: QtObject { //here in order to not be accessible from outside property bool completed: false property SequentialAnimation positionResetAnim: SequentialAnimation { id: positionResetAnim property alias to: internalAnim.to NumberAnimation { id: internalAnim target: root to: drawerOpen ? 1 : 0 property: "position" duration: (root.position)*Units.longDuration } ScriptAction { script: { root.drawerOpen = internalAnim.to != 0; } } } readonly property Item statesItem: Item { states: [ State { when: root.collapsed PropertyChanges { target: root implicitWidth: edge == Qt.TopEdge || edge == Qt.BottomEdge ? applicationWindow().width : Math.min(collapsedSize + leftPadding + rightPadding, Math.round(applicationWindow().width*0.8)) implicitHeight: edge == Qt.LeftEdge || edge == Qt.RightEdge ? applicationWindow().height : Math.min(collapsedSize + topPadding + bottomPadding, Math.round(applicationWindow().height*0.8)) } }, State { when: !root.collapsed PropertyChanges { target: root implicitWidth: edge == Qt.TopEdge || edge == Qt.BottomEdge ? applicationWindow().width : Math.min(contentItem.implicitWidth, Math.round(applicationWindow().width*0.8)) implicitHeight: edge == Qt.LeftEdge || edge == Qt.RightEdge ? applicationWindow().height : Math.min(contentHeight + topPadding + bottomPadding, Math.round(applicationWindow().height*0.4)) contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0) contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0) } } ] transitions: Transition { reversible: true NumberAnimation { properties: root.edge == Qt.TopEdge || root.edge == Qt.BottomEdge ? "implicitHeight" : "implicitWidth" duration: Units.longDuration easing.type: Easing.InOutQuad } } } } }