diff --git a/src/controls/PageRow.qml b/src/controls/PageRow.qml index e5e90300..be4bf21e 100644 --- a/src/controls/PageRow.qml +++ b/src/controls/PageRow.qml @@ -1,768 +1,776 @@ /* * 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.5 import QtQuick.Layouts 1.2 import QtQml.Models 2.2 import QtQuick.Templates 2.0 as T import QtQuick.Controls 2.0 as QQC2 import org.kde.kirigami 2.4 import "private" as Private import "templates/private" as TemplatesPrivate import "templates" as KT /** * PageRow implements a row-based navigation model, which can be used * with a set of interlinked information pages. Items are pushed in the * back of the row and the view scrolls until that row is visualized. * A PageRowcan show a single page or a multiple set of columns, depending * on the window width: on a phone a single column should be fullscreen, * while on a tablet or a desktop more than one column should be visible. * @inherit QtQuick.Templates.Control */ T.Control { id: root //BEGIN PROPERTIES /** * This property holds the number of items currently pushed onto the view */ readonly property int depth: popScrollAnim.running && popScrollAnim.pendingDepth > -1 ? popScrollAnim.pendingDepth : pagesLogic.count /** * The last Page in the Row */ readonly property Item lastItem: pagesLogic.count ? pagesLogic.get(pagesLogic.count - 1).page : null /** * The currently visible Item */ readonly property Item currentItem: mainView.currentItem ? mainView.currentItem.page : null /** * the index of the currently visible Item */ property alias currentIndex: mainView.currentIndex /** * The initial item when this PageRow is created */ property variant initialPage /** * The main flickable of this Row */ contentItem: mainView /** * The default width for a column * default is wide enough for 30 grid units. * Pages can override it with their Layout.fillWidth, * implicitWidth Layout.minimumWidth etc. */ property int defaultColumnWidth: Units.gridUnit * 20 /** * interactive: bool * If true it will be possible to go back/forward by dragging the * content themselves with a gesture. * Otherwise the only way to go back will be programmatically * default: true */ property alias interactive: mainView.interactive /** * wideMode: bool * If true, the PageRow is wide enough that willshow more than one column at once * @since 5.37 */ readonly property bool wideMode: root.width >= root.defaultColumnWidth*2 && pagesLogic.count >= 2 /** * separatorVisible: bool * True if the separator between pages should be visible * default: true * @since 5.38 */ property bool separatorVisible: true /** * globalToolBar: grouped property * Controls the appearance of an optional global toolbar for the whole PageRow. * It's a grouped property comprised of the following properties: * * style: (Kirigami.ApplicationHeaderStyle) can have the following values: * ** Auto: depending on application formfactor, it can behave automatically like other values, such as a Breadcrumb on mobile and ToolBar on desktop * ** Breadcrumb: it will show a breadcrumb of all the page titles in the stack, for easy navigation * ** Titles: each page will only have its own tile on top * ** TabBar: the global toolbar will look like a TabBar to select the pages * ** ToolBar: each page will have the title on top together buttons and menus to represent all of the page actions: not available on Mobile systems. * ** None: no global toolbar will be shown * * * actualStyle: this will represent the actual style of the toolbar: it can be different from style in the case style is Auto * * showNavigationButtons: if true, forward and backward navigation buttons will be shown on the left of the toolbar * @since 5.48 */ readonly property alias globalToolBar: globalToolBar //END PROPERTIES //BEGIN FUNCTIONS /** * Pushes a page on the stack. * The page can be defined as a component, item or string. * If an item is used then the page will get re-parented. * If a string is used then it is interpreted as a url that is used to load a page * component. * * @param page The page can also be given as an array of pages. * In this case all those pages will * be pushed onto the stack. The items in the stack can be components, items or * strings just like for single pages. * Additionally an object can be used, which specifies a page and an optional * properties property. * This can be used to push multiple pages while still giving each of * them properties. * When an array is used the transition animation will only be to the last page. * * @param properties The properties argument is optional and allows defining a * map of properties to set on the page. * @return The new created page */ function push(page, properties) { //don't push again things already there if (page.createObject === undefined && typeof page != "string" && pagesLogic.containsPage(page)) { print("The item " + page + " is already in the PageRow"); return; } if (popScrollAnim.running) { popScrollAnim.running = false; popScrollAnim.popPageCleanup(popScrollAnim.pendingPage); } popScrollAnim.popPageCleanup(currentItem); // figure out if more than one page is being pushed var pages; if (page instanceof Array) { pages = page; page = pages.pop(); if (page.createObject === undefined && page.parent === undefined && typeof page != "string") { properties = properties || page.properties; page = page.page; } } // push any extra defined pages onto the stack if (pages) { var i; for (i = 0; i < pages.length; i++) { var tPage = pages[i]; var tProps; if (tPage.createObject === undefined && tPage.parent === undefined && typeof tPage != "string") { if (pagesLogic.containsPage(tPage)) { print("The item " + page + " is already in the PageRow"); continue; } tProps = tPage.properties; tPage = tPage.page; } var container = pagesLogic.initPage(tPage, tProps); pagesLogic.append(container); } } // initialize the page var container = pagesLogic.initPage(page, properties); pagesLogic.append(container); container.visible = container.page.visible = true; mainView.currentIndex = container.level; return container.page } /** * Pops a page off the stack. * @param page If page is specified then the stack is unwound to that page, * to unwind to the first page specify * page as null. * @return The page instance that was popped off the stack. */ function pop(page) { if (depth == 0) { return; } //if a pop was animating, stop it if (popScrollAnim.running) { popScrollAnim.running = false; popScrollAnim.popPageCleanup(popScrollAnim.pendingPage); //if a push was animating, stop it } else { mainView.positionViewAtIndex(mainView.currentIndex, ListView.Beginning); } popScrollAnim.from = mainView.contentX if ((!page || !page.parent) && pagesLogic.count > 1) { page = pagesLogic.get(pagesLogic.count - 2).page; } popScrollAnim.to = page && page.parent ? page.parent.x : 0; popScrollAnim.pendingPage = page; popScrollAnim.pendingDepth = page && page.parent ? page.parent.level + 1 : 0; popScrollAnim.running = true; } SequentialAnimation { id: popScrollAnim property real from property real to property var pendingPage property int pendingDepth: -1 function popPageCleanup(page) { if (pagesLogic.count == 0) { return; } if (popScrollAnim.running) { popScrollAnim.running = false; } var oldPage = pagesLogic.get(pagesLogic.count-1).page; if (page !== undefined) { // an unwind target has been specified - pop until we find it while (page != oldPage && pagesLogic.count > 1) { pagesLogic.removePage(oldPage.parent.level); oldPage = pagesLogic.get(pagesLogic.count-1).page; } } else { pagesLogic.removePage(pagesLogic.count-1); } } NumberAnimation { target: mainView properties: "contentX" duration: Units.shortDuration from: popScrollAnim.from to: popScrollAnim.to } ScriptAction { script: { //snap mainView.flick(100, 0) popScrollAnim.popPageCleanup(popScrollAnim.pendingPage); } } } /** * Replaces a page on the stack. * @param page The page can also be given as an array of pages. * In this case all those pages will * be pushed onto the stack. The items in the stack can be components, items or * strings just like for single pages. * Additionally an object can be used, which specifies a page and an optional * properties property. * This can be used to push multiple pages while still giving each of * them properties. * When an array is used the transition animation will only be to the last page. * @param properties The properties argument is optional and allows defining a * map of properties to set on the page. * @see push() for details. */ function replace(page, properties) { if (currentIndex>=1) popScrollAnim.popPageCleanup(pagesLogic.get(currentIndex-1).page); else if (currentIndex==0) popScrollAnim.popPageCleanup(); else console.warn("There's no page to replace"); return push(page, properties); } /** * Clears the page stack. * Destroy (or reparent) all the pages contained. */ function clear() { return pagesLogic.clearPages(); } /** * @return the page at idx * @param idx the depth of the page we want */ function get(idx) { return pagesLogic.get(idx).page; } /** * go back to the previous index and scroll to the left to show one more column */ function flickBack() { if (depth > 1) { currentIndex = Math.max(0, currentIndex - 1); } if (LayoutMirroring.enabled) { if (!mainView.atEnd) { mainViewScrollAnim.from = mainView.contentX mainViewScrollAnim.to = Math.min(mainView.contentWidth - mainView.width, mainView.contentX + defaultColumnWidth) mainViewScrollAnim.running = true; } } else { if (mainView.contentX - mainView.originX > 0) { mainViewScrollAnim.from = mainView.contentX mainViewScrollAnim.to = Math.max(mainView.originX, mainView.contentX - defaultColumnWidth) mainViewScrollAnim.running = true; } } } /** * layers: QtQuick.Controls.PageStack * Access to the modal layers. * Sometimes an application needs a modal page that always covers all the rows. * For instance the full screen image of an image viewer or a settings page. * @since 5.38 */ property alias layers: layersStack //END FUNCTIONS onInitialPageChanged: { clear(); if (initialPage) { push(initialPage, null) } } Keys.forwardTo: [currentItem] SequentialAnimation { id: mainViewScrollAnim property real from property real to NumberAnimation { target: mainView properties: "contentX" duration: Units.longDuration from: mainViewScrollAnim.from to: mainViewScrollAnim.to } ScriptAction { script: mainView.flick(100, 0) } } Private.GlobalToolBarStyleGroup { id: globalToolBar } QQC2.StackView { id: layersStack z: 99 anchors.fill: parent initialItem: mainView function clear () { //don't let it kill the main page row var d = root.depth; for (var i = 1; i < d; ++i) { pop(); } } popEnter: Transition { YAnimator { from: -height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } popExit: Transition { YAnimator { from: 0 to: height duration: Units.longDuration easing.type: Easing.OutCubic } } pushEnter: Transition { YAnimator { from: height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } pushExit: Transition { YAnimator { from: 0 to: -height duration: Units.longDuration easing.type: Easing.OutCubic } } replaceEnter: Transition { YAnimator { from: height to: 0 duration: Units.longDuration easing.type: Easing.OutCubic } } replaceExit: Transition { YAnimator { from: 0 to: -height duration: Units.longDuration easing.type: Easing.OutCubic } } } ListView { id: mainView boundsBehavior: Flickable.StopAtBounds orientation: Qt.Horizontal snapMode: ListView.SnapToItem currentIndex: root.currentIndex property int marginForLast: count > 1 ? pagesLogic.get(count-1).page.width - pagesLogic.get(count-1).width : 0 leftMargin: LayoutMirroring.enabled ? marginForLast : 0 rightMargin: LayoutMirroring.enabled ? 0 : marginForLast preferredHighlightBegin: 0 preferredHighlightEnd: 0 highlightMoveDuration: Units.longDuration highlightFollowsCurrentItem: true onMovementEnded: currentIndex = Math.max(0, indexAt(contentX, 0)) onFlickEnded: onMovementEnded(); onCurrentIndexChanged: { if (currentItem) { currentItem.page.forceActiveFocus(); } } model: ObjectModel { id: pagesLogic readonly property var componentCache: new Array() readonly property int roundedDefaultColumnWidth: root.width < root.defaultColumnWidth*2 ? root.width : root.defaultColumnWidth function removePage(id) { if (id < 0 || id >= count) { print("Tried to remove an invalid page index:" + id); return; } var item = pagesLogic.get(id); if (item.owner) { item.page.visible = false; item.page.parent = item.owner; } //FIXME: why reparent ing is necessary? //is destroy just an async deleteLater() that isn't executed immediately or it actually leaks? pagesLogic.remove(id); item.parent = root; if (item.page.parent==item) { item.page.destroy(1) } item.destroy(); } function clearPages () { popScrollAnim.running = false; popScrollAnim.pendingDepth = -1; while (count > 0) { removePage(count-1); } } function initPage(page, properties) { var container = containerComponent.createObject(mainView, { "level": pagesLogic.count, "page": page }); var pageComp; if (page.createObject) { // page defined as component pageComp = page; } else if (typeof page == "string") { // page defined as string (a url) pageComp = pagesLogic.componentCache[page]; if (!pageComp) { pageComp = pagesLogic.componentCache[page] = Qt.createComponent(page); } } if (pageComp) { if (pageComp.status == Component.Error) { throw new Error("Error while loading page: " + pageComp.errorString()); } else { // instantiate page from component page = pageComp.createObject(container.pageParent, properties || {}); } } else { // copy properties to the page for (var prop in properties) { if (properties.hasOwnProperty(prop)) { page[prop] = properties[prop]; } } } container.page = page; if (page.parent == null || page.parent == container.pageParent) { container.owner = null; } // the page has to be reparented if (page.parent != container) { page.parent = container; } return container; } function containsPage(page) { for (var i = 0; i < pagesLogic.count; ++i) { var candidate = pagesLogic.get(i); if (candidate.page == page) { print("The item " + page + " is already in the PageRow"); return; } } } } T.ScrollIndicator.horizontal: T.ScrollIndicator { anchors { left: parent.left right: parent.right bottom: parent.bottom } height: Units.smallSpacing contentItem: Rectangle { height: Units.smallSpacing width: Units.smallSpacing color: Theme.textColor opacity: 0 onXChanged: { opacity = 0.3 scrollIndicatorTimer.restart(); } Behavior on opacity { OpacityAnimator { duration: Units.longDuration easing.type: Easing.InOutQuad } } Timer { id: scrollIndicatorTimer interval: Units.longDuration * 4 onTriggered: parent.opacity = 0; } } } AbstractApplicationHeader { id: globalToolBarUI readonly property int leftReservedSpace: buttonsLayout.visible && buttonsLayout.visibleChildren.length > 1 ? buttonsLayout.width : 0 visible: globalToolBar.actualStyle != ApplicationHeaderStyle.None height: visible ? implicitHeight : 0 preferredHeight: 42 maximumHeight: preferredHeight anchors { left: parent.left top: parent.top right: parent.right } RowLayout { anchors { fill: parent bottomMargin: 1 } spacing: 0 RowLayout { id: buttonsLayout visible: globalToolBar.actualStyle != ApplicationHeaderStyle.TabBar && globalToolBar.actualStyle != ApplicationHeaderStyle.None - + + Item { + visible: typeof applicationWindow() !== "undefined" && applicationWindow().globalDrawer.handle.handleAnchor == root + Layout.preferredWidth: backButton.background.implicitHeight + Layout.preferredHeight: backButton.background.implicitHeight + } TemplatesPrivate.BackButton { - Layout.fillHeight: true + id: backButton + Layout.preferredWidth: background.implicitHeight + Layout.preferredHeight: background.implicitHeight } TemplatesPrivate.ForwardButton { - Layout.fillHeight: true + Layout.preferredWidth: background.implicitHeight + Layout.preferredHeight: background.implicitHeight } Separator { Layout.preferredHeight: parent.height * 0.6 //FIXME: hacky opacity: buttonsLayout.visibleChildren.length > 1 } } Loader { id: breadcrumbLoader Layout.fillWidth: true Layout.fillHeight: true active: globalToolBar.actualStyle == ApplicationHeaderStyle.TabBar || globalToolBar.actualStyle == ApplicationHeaderStyle.Breadcrumb //TODO: different implementation? sourceComponent: ApplicationHeader { backButtonEnabled: false anchors.fill:parent background.visible: false headerStyle: globalToolBar.style } } } background.visible: breadcrumbLoader.active } onContentWidthChanged: mainView.positionViewAtIndex(root.currentIndex, ListView.Contain) } Component { id: containerComponent MouseArea { id: container height: mainView.height width: root.width state: page ? (!root.wideMode ? "vertical" : (container.level >= pagesLogic.count - 1 ? "last" : "middle")) : ""; acceptedButtons: Qt.LeftButton | Qt.BackButton | Qt.ForwardButton property int level readonly property int hint: page && page.implicitWidth ? page.implicitWidth : root.defaultColumnWidth readonly property int roundedHint: Math.floor(root.width/hint) > 0 ? root.width/Math.floor(root.width/hint) : root.width property Item header: header Loader { id: header z: 999 y: globalToolBarUI.height - height height: globalToolBarUI.preferredHeight anchors { left: page ? page.left : parent.left right: page? page.right : parent.right } active: globalToolBar.actualStyle == ApplicationHeaderStyle.ToolBar || globalToolBar.actualStyle == ApplicationHeaderStyle.Titles function syncSource() { if (container.page && active) { header.setSource(Qt.resolvedUrl(globalToolBar.actualStyle == ApplicationHeaderStyle.ToolBar ? "private/ToolBarPageHeader.qml" : "private/TitlesPageHeader.qml"), {"container": container, "page": container.page, "current": Qt.binding(function() {return root.currentIndex == container.level})}); } } Connections { target: globalToolBar onActualStyleChanged: header.syncSource() } } Separator { z: 999 anchors.verticalCenter: header.verticalCenter height: header.height * 0.6 visible: mainView.contentX < container.x && globalToolBarUI.visible Theme.textColor: globalToolBarUI.Theme.textColor } property Item footer property Item page onPageChanged: { if (page) { owner = page.parent; page.parent = container; page.anchors.left = container.left; page.anchors.top = header.bottom; page.anchors.right = container.right; page.anchors.bottom = container.bottom; header.syncSource() } } property Item owner drag.filterChildren: true onClicked: { switch (mouse.button) { case Qt.BackButton: root.flickBack(); break; case Qt.ForwardButton: root.currentIndex = Math.min(root.depth, root.currentIndex + 1); break; default: root.currentIndex = level; break; } } onFocusChanged: { if (focus) { root.currentIndex = level; } } Separator { z: 999 anchors { top: header.bottom bottom: parent.bottom left: parent.left //ensure a sharp angle topMargin: -width } visible: root.separatorVisible && mainView.contentX < container.x } states: [ State { name: "vertical" PropertyChanges { target: container width: root.width } PropertyChanges { target: container.page ? container.page.anchors : null rightMargin: 0 } }, State { name: "last" PropertyChanges { target: container width: pagesLogic.roundedDefaultColumnWidth } PropertyChanges { target: container.page.anchors rightMargin: { return -(root.width - pagesLogic.roundedDefaultColumnWidth*2); } } }, State { name: "middle" PropertyChanges { target: container width: pagesLogic.roundedDefaultColumnWidth } PropertyChanges { target: container.page.anchors rightMargin: 0 } } ] } } } diff --git a/src/controls/templates/OverlayDrawer.qml b/src/controls/templates/OverlayDrawer.qml index 49862f10..dcd225e4 100644 --- a/src/controls/templates/OverlayDrawer.qml +++ b/src/controls/templates/OverlayDrawer.qml @@ -1,367 +1,369 @@ /* * 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.4 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) ): 0 //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 /** * 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: desktopMode parent: applicationWindow().overlay.parent + property Item handleAnchor: (!Settings.isMobile && applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar && applicationWindow().pageStack.globalToolBar.actualStyle != ApplicationHeaderStyle.None) + ? applicationWindow().pageStack + : (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") !== -1 ? applicationWindow().header : null) property int startX property int mappedStartX - property bool desktopMode: applicationWindow() && applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") !== -1 + property bool desktopMode: handleAnchor enabled: root.handleVisible && root.modal 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; case Qt.RightEdge: return drawerHandle.parent.width - (root.background.width * root.position) - width; default: return 0; } } + y: handleAnchor ? handleAnchor.mapToItem(root.contentItem.parent, 0, handleAnchor.y).y : 0 anchors { - top: drawerHandle.desktopMode ? parent.top : undefined - bottom: drawerHandle.desktopMode ? undefined : parent.bottom bottomMargin: { if (!applicationWindow()) { return; } var margin = 0; if (applicationWindow().footer) { margin = applicationWindow().footer.height; } 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: Units.iconSizes.medium + Units.smallSpacing*2 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: Theme.View 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 parent: modal ? T2.ApplicationWindow.overlay : T2.ApplicationWindow.contentItem height: edge == Qt.LeftEdge || edge == Qt.RightEdge ? applicationWindow().height : Math.min(contentItem.implicitHeight, Math.round(applicationWindow().height*0.8)) width: edge == Qt.TopEdge || edge == Qt.BottomEdge ? applicationWindow().width : Math.min(contentItem.implicitWidth, Math.round(applicationWindow().width*0.8)) edge: Qt.LeftEdge modal: true dragMargin: enabled && (edge == Qt.LeftEdge || edge == Qt.RightEdge) ? Qt.styleHints.startDragDistance : 0 implicitWidth: Math.max(background ? background.implicitWidth : 0, contentWidth + leftPadding + rightPadding) implicitHeight: Math.max(background ? background.implicitHeight : 0, contentHeight + topPadding + bottomPadding) contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0) contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0) 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 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 { positionResetAnim.to = 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 NumberAnimation positionResetAnim: NumberAnimation { id: positionResetAnim target: root to: 0 property: "position" duration: (root.position)*Units.longDuration } } }