diff --git a/src/controls/ApplicationWindow.qml b/src/controls/ApplicationWindow.qml index 80b2b9be..36957728 100644 --- a/src/controls/ApplicationWindow.qml +++ b/src/controls/ApplicationWindow.qml @@ -1,174 +1,176 @@ /* * 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.5 import "templates/private" import org.kde.kirigami 2.4 as Kirigami import QtGraphicalEffects 1.0 /** * A window that provides some basic features needed for all apps * * It's usually used as a root QML component for the application. * It's based around the PageRow component, the application will be * about pages adding and removal. * For most of the usages, this class should be used instead * of AbstractApplicationWidnow * @see AbstractApplicationWidnow * * 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" * } * ] * } * * contextDrawer: Kirigami.ContextDrawer { * id: contextDrawer * } * * pageStack.initialPage: Kirigami.Page { * mainAction: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * contextualActions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * [...] * } * [...] * } * @endcode * */ AbstractApplicationWindow { id: root /** * pageStack: StackView * Readonly. * The stack used to allocate the pages and to manage the transitions * between them. * It's using a PageRow, while having the same API as PageStack, * it positions the pages as adjacent columns, with as many columns * as can fit in the screen. An handheld device would usually have a single * fullscreen column, a tablet device would have many tiled columns. */ property alias pageStack: __pageStack //redefines here as here we can know a pointer to PageRow wideScreen: width >= applicationWindow().pageStack.defaultColumnWidth * 1.5 PageRow { id: __pageStack globalToolBar.style: Kirigami.ApplicationHeaderStyle.Auto anchors { fill: parent //HACK: workaround a bug in android iOS keyboard management bottomMargin: ((Qt.platform.os == "android" || Qt.platform.os == "ios") || !Qt.inputMethod.visible) ? 0 : Qt.inputMethod.keyboardRectangle.height onBottomMarginChanged: { if (bottomMargin > 0) { root.reachableMode = false; } } } //FIXME onCurrentIndexChanged: root.reachableMode = false; function goBack() { //NOTE: drawers are handling the back button by themselves var backEvent = {accepted: false} if (root.pageStack.layers.depth > 1) { root.pageStack.layers.currentItem.backRequested(backEvent); if (!backEvent.accepted) { root.pageStack.layers.pop(); backEvent.accepted = true; } - } else if (root.pageStack.currentIndex >= 1) { + } else { root.pageStack.currentItem.backRequested(backEvent); - if (!backEvent.accepted) { - root.pageStack.flickBack(); - backEvent.accepted = true; + if (root.pageStack.currentIndex >= 1) { + if (!backEvent.accepted) { + root.pageStack.flickBack(); + backEvent.accepted = true; + } } } if (Kirigami.Settings.isMobile && !backEvent.accepted && Qt.platform.os !== "ios") { Qt.quit(); } } function goForward() { root.pageStack.currentIndex = Math.min(root.pageStack.depth-1, root.pageStack.currentIndex + 1); } Keys.onBackPressed: { goBack(); event.accepted = true } Shortcut { sequence: "Forward" onActivated: __pageStack.goForward(); } Shortcut { sequence: StandardKey.Forward onActivated: __pageStack.goForward(); } Shortcut { sequence: StandardKey.Back onActivated: __pageStack.goBack(); } focus: true } } diff --git a/src/controls/Page.qml b/src/controls/Page.qml index b6cfc359..4c41b6c9 100644 --- a/src/controls/Page.qml +++ b/src/controls/Page.qml @@ -1,332 +1,347 @@ /* * 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 as Kirigami import "private" import QtQuick.Templates 2.0 as T2 /** * Page is a container for all the app pages: everything pushed to the * ApplicationWindow stackView should be a Page instabnce (or a subclass, * such as ScrollablePage) * @see ScrollablePage * @inherit QtQuick.Templates.Page */ T2.Page { id: root /** * leftPadding: int * default contents padding at left */ leftPadding: Kirigami.Units.gridUnit /** * topPadding: int * default contents padding at top */ topPadding: Kirigami.Units.gridUnit /** * rightPadding: int * default contents padding at right */ rightPadding: Kirigami.Units.gridUnit /** * bottomPadding: int * default contents padding at bottom */ bottomPadding: actionButtons.item ? actionButtons.height : Kirigami.Units.gridUnit /** * flickable: Flickable * if the central element of the page is a Flickable * (ListView and Gridview as well) you can set it there. * normally, you wouldn't need to do that, but just use the * ScrollablePage element instead * @see ScrollablePage * Use this if your flickable has some non standard properties, such as not covering the whole Page */ property Flickable flickable /** * actions.contextualActions: list * Defines the contextual actions for the page: * an easy way to assign actions in the right sliding panel * * 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 { * [...] * actions.contextualActions: [ * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * }, * Kirigami.Action { * iconName: "edit" * text: "Action text" * onTriggered: { * // do stuff * } * } * ] * [...] * } * @endcode */ //TODO: remove property alias contextualActions: actionsGroup.contextualActions /** * actions.main: Action * An optional single action for the action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.main: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias mainAction: actionsGroup.main /** * actions.left: Action * An optional extra action at the left of the main action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.left: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias leftAction: actionsGroup.left /** * actions.right: Action * An optional extra action at the right of the main action button. * it can be a Kirigami.Action or a QAction * * Example usage: * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions.right: Kirigami.Action { * iconName: "edit" * onTriggered: { * // do stuff * } * } * } * @endcode */ //TODO: remove property alias rightAction: actionsGroup.right /** * Actions properties are grouped. * * @code * import org.kde.kirigami 2.4 as Kirigami * Kirigami.Page { * actions { * main: Kirigami.Action {...} * left: Kirigami.Action {...} * right: Kirigami.Action {...} * contextualActions: [ * Kirigami.Action {...}, * Kirigami.Action {...} * ] * } * } * @endcode */ readonly property alias actions: actionsGroup /** * isCurrentPage: bool * * Specifies if it's the currently selected page in the window's pages row. * * @since 2.1 */ readonly property bool isCurrentPage: typeof applicationWindow === "undefined" || !globalToolBar.row ? true : (globalToolBar.row.layers.depth > 1 ? globalToolBar.row.layers.currentItem == root : globalToolBar.row.currentItem == root) PageActionPropertyGroup { id: actionsGroup } + /** + * overlay: Item + * an item which stays on top of every other item in the page, + * if you want to make sure some elements are completely in a + * layer on top of the whole content, parent items to this one. + * It's a "local" version of ApplicationWindow's overlay + * @since 2.5 + */ + readonly property alias overlay: overlayItem + /** * emitted When the application requests a Back action * For instance a global "back" shortcut or the Android * Back button has been pressed. * The page can manage the back event by itself, * and if it set event.accepted = true, it will stop the main * application to manage the back event. */ signal backRequested(var event); - //NOTE: This exists just because control instances require it - contentItem: Item { - onChildrenChanged: { - //NOTE: make sure OverlaySheets are directly under the root - //so they are over all the contents and don't have margins - //search for an OverlaySheet, unfortunately have to blind test properties - //as there is no way to get the classname from qml objects - //TODO: OverlaySheets should be Popup instead? - for (var i = children.length -1; i >= 0; --i) { - var child = children[i]; - if (child.toString().indexOf("OverlaySheet") === 0 || - (child.sheetOpen !== undefined && child.open !== undefined && child.close !== undefined)) { - child.parent = root; - child.z = 9997 - } + // Look for sheets and cose them + //FIXME: port Sheets to Popup? + onBackRequested: { + for(var i in root.resources) { + var item = root.resources[i]; + if (item.hasOwnProperty("close") && item.hasOwnProperty("sheetOpen") && item.sheetOpen) { + item.close() + event.accepted = true; + return; } } } + //NOTE: This exists just because control instances require it + contentItem: Item { + } + //FIXME: on material the shadow would bleed over clip: root.header != null; Component.onCompleted: { parentChanged(root.parent); } onParentChanged: { if (!parent) { return; } globalToolBar.stack = null; globalToolBar.row = null; if (root.parent.hasOwnProperty("__pageRow")) { globalToolBar.row = root.parent.__pageRow; } if (root.T2.StackView.view) { globalToolBar.stack = root.T2.StackView.view; globalToolBar.row = root.T2.StackView.view.parent; } if (globalToolBar.row) { globalToolBar.row.globalToolBar.actualStyleChanged.connect(globalToolBar.syncSource); globalToolBar.syncSource(); } } + Item { + id: overlayItem + parent: root + z: 9998 + anchors.fill: parent + } + //global top toolbar if we are in a PageRow (in the row or as a layer) Loader { id: globalToolBar z: 9999 parent: root.clip ? root.parent : root height: item ? item.implicitHeight : 0 anchors { left: parent ? root.left : undefined right: parent ? root.right : undefined bottom: parent ? root.top : undefined } property Kirigami.PageRow row property T2.StackView stack active: row && (row.globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.ToolBar || globalToolBar.row.globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.Titles) function syncSource() { if (row && active) { setSource(Qt.resolvedUrl(row.globalToolBar.actualStyle == Kirigami.ApplicationHeaderStyle.ToolBar ? "private/ToolBarPageHeader.qml" : "private/TitlesPageHeader.qml"), //TODO: find container reliably, remove assumption {"pageRow": Qt.binding(function() {return row}), "page": root, "current": Qt.binding(function() {return stack || !root.parent ? true : row.currentIndex == root.parent.level})}); } } Separator { z: 999 anchors.verticalCenter: globalToolBar.verticalCenter height: globalToolBar.height * 0.6 visible: globalToolBar.row && root.parent && globalToolBar.row.contentItem.contentX < root.parent.x - globalToolBar.row.globalToolBar.leftReservedSpace Kirigami.Theme.textColor: globalToolBar.item ? globalToolBar.item.Kirigami.Theme.textColor : undefined } } //bottom action buttons Loader { id: actionButtons z: 9999 parent: root anchors { left: parent.left right: parent.right bottom: parent.bottom } //It should be T2.Page, Qt 5.7 doesn't like it property Item page: root height: item ? item.height : 0 active: typeof applicationWindow !== "undefined" && (!globalToolBar.row || globalToolBar.row.globalToolBar.actualStyle != Kirigami.ApplicationHeaderStyle.ToolBar) && //Legacy (typeof applicationWindow === "undefined" || (!applicationWindow().header || applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === -1) && (!applicationWindow().footer || applicationWindow().footer.toString().indexOf("ToolBarApplicationHeader") === -1)) source: Qt.resolvedUrl("./private/ActionButton.qml") } Layout.fillWidth: true } diff --git a/src/controls/ScrollablePage.qml b/src/controls/ScrollablePage.qml index 0521ddae..54c4cfca 100644 --- a/src/controls/ScrollablePage.qml +++ b/src/controls/ScrollablePage.qml @@ -1,180 +1,180 @@ /* * 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" /** * ScrollablePage is a container for all the app pages: everything pushed to the * ApplicationWindow stackView should be a Page or ScrollablePage instabnce. * This Page subclass is for content that has to be scrolled around, such as * bigger content than the screen that would normally go in a Flickable * or a ListView. * Scrolling and scrolling indicators will be automatically managed * * * @code * ScrollablePage { * id: root * //The rectangle will automatically bescrollable * Rectangle { * width: root.width * height: 99999 * } * } * @endcode * * Another behavior added by this class is a "scroll down to refresh" behavior * It also can give the contents of the flickable to have more top margins in order * to make possible to scroll down the list to reach it with the thumb while using the * phone with a single hand. * * Implementations should handle the refresh themselves as follows * * @code * Kirigami.ScrollablePage { * id: view * supportsRefreshing: true * onRefreshingChanged: { * if (refreshing) { * myModel.refresh(); * } * } * ListView { * //NOTE: MyModel doesn't come from the components, * //it's purely an example on how it can be used together * //some application logic that can update the list model * //and signals when it's done. * model: MyModel { * onRefreshDone: view.refreshing = false; * } * delegate: BasicListItem {} * } * } * [...] * @endcode * */ Page { id: root /** * refreshing: bool * If true the list is asking for refresh and will show a loading spinner. * it will automatically be set to true when the user pulls down enough the list. * This signals the application logic to start its refresh procedure. * The application itself will have to set back this property to false when done. */ property alias refreshing: scrollView.refreshing /** * supportsRefreshing: bool * If true the list supports the "pull down to refresh" behavior. * default is false. */ property alias supportsRefreshing: scrollView.supportsRefreshing /** * flickable: Flickable * The main Flickable item of this page */ property alias flickable: scrollView.flickableItem /** * verticalScrollBarPolicy: Qt.ScrollBarPolicy * The vertical scrollbar policy */ property alias verticalScrollBarPolicy: scrollView.verticalScrollBarPolicy /** * horizontalScrollBarPolicy: Qt.ScrollBarPolicy * The horizontal scrollbar policy */ property alias horizontalScrollBarPolicy: scrollView.horizontalScrollBarPolicy /** * The main content Item of this page. * In the case of a ListView or GridView, both contentItem and flickable * will be a pointer to the ListView (or GridView) * NOTE: can't be contentItem as Page's contentItem is final */ default property QtObject mainItem /** * keyboardNavigationEnabled: bool * If true, and if flickable is an item view, like a ListView or * a GridView, it will be possible to navigate the list current item * to next and previous items with keyboard up/down arrow buttons. * Also, any key event will be forwarded to the current list item. * default is true. */ property bool keyboardNavigationEnabled: true Theme.colorSet: flickable && flickable.hasOwnProperty("model") ? Theme.View : Theme.Window RefreshableScrollView { id: scrollView + //NOTE: here to not expose it to public api + property QtObject oldMainItem z: 0 //child of root as it shouldn't have margins parent: root page: root clip: root.clip topPadding: contentItem == flickableItem ? 0 : root.topPadding leftPadding: root.leftPadding rightPadding: root.rightPadding bottomPadding: contentItem == flickableItem ? 0 : root.bottomPadding anchors { fill: parent topMargin: root.header ? root.header.height : 0 bottomMargin: root.footer ? root.footer.height : 0 } } - anchors.topMargin: 0 Keys.forwardTo: root.keyboardNavigationEnabled && root.flickable ? (("currentItem" in root.flickable) && root.flickable.currentItem ? [ root.flickable.currentItem, root.flickable ] : [ root.flickable ]) : [] - Item { - id: overlay - parent: root - z: 9998 - anchors.fill: parent - property QtObject oldMainItem - } //HACK to get the mainItem as the last one, all the other eventual items as an overlay //no idea if is the way the user expects onMainItemChanged: { if (mainItem.hasOwnProperty("anchors")) { scrollView.contentItem = mainItem //don't try to reparent drawers } else if (mainItem.hasOwnProperty("dragMargin")) { return; + //reparent sheets + } else if (mainItem.hasOwnProperty("sheetOpen")) { + mainItem.parent = root; + root.data.push(mainItem); + return; + } + + if (scrollView.oldMainItem && scrollView.oldMainItem.hasOwnProperty("parent") && scrollView.oldMainItem.parent != applicationWindow().overlay) { + scrollView.oldMainItem.parent = overlay } - if (overlay.oldMainItem && overlay.oldMainItem.hasOwnProperty("parent") && overlay.oldMainItem.parent != applicationWindow().overlay) { - overlay.oldMainItem.parent = overlay - } - overlay.oldMainItem = mainItem + scrollView.oldMainItem = mainItem } } diff --git a/src/controls/templates/OverlaySheet.qml b/src/controls/templates/OverlaySheet.qml index ab8f4337..7cd076fa 100644 --- a/src/controls/templates/OverlaySheet.qml +++ b/src/controls/templates/OverlaySheet.qml @@ -1,530 +1,530 @@ /* * Copyright (C) 2016 by 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 2.010-1301, USA. */ import QtQuick 2.5 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import org.kde.kirigami 2.4 import QtGraphicalEffects 1.0 import QtQuick.Templates 2.0 as T2 import "private" import "../private" /** * An overlay sheet that covers the current Page content. * Its contents can be scrolled up or down, scrolling all the way up or * all the way down, dismisses it. * Use this for big, modal dialogs or information display, that can't be * logically done as a new separate Page, even if potentially * are taller than the screen space. * @inherits: QtQuick.QtObject */ QtObject { id: root Theme.colorSet: Theme.View Theme.inherit: false /** * contentItem: Item * This property holds the visual content item. * * Note: The content item is automatically resized inside the * padding of the control. * Conversely, the Sheet will be sized based on the size hints * of the contentItem, so if you need a custom size sheet, * redefine contentWidth and contentHeight of your contentItem */ default property Item contentItem /** * sheetOpen: bool * If true the sheet is open showing the contents of the OverlaySheet * component. */ property bool sheetOpen /** * leftPadding: int * default contents padding at left */ property int leftPadding: Units.gridUnit /** * topPadding: int * default contents padding at top */ property int topPadding: Units.gridUnit /** * rightPadding: int * default contents padding at right */ property int rightPadding: Units.gridUnit /** * bottomPadding: int * default contents padding at bottom */ property int bottomPadding: Units.gridUnit /** * header: Item * an optional item which will be used as the sheet's header, * always kept on screen * @since 5.43 */ property Item header /** * header: Item * an optional item which will be used as the sheet's footer, * always kept on screen * @since 5.43 */ property Item footer /** * background: Item * This property holds the background item. * * Note: If the background item has no explicit size specified, * it automatically follows the control's size. * In most cases, there is no need to specify width or * height for a background item. */ property Item background /** * showCloseButton: bool * whether to show the close button in the top-right corner * @since 5.44 */ property alias showCloseButton: closeIcon.visible property Item parent function open() { openAnimation.from = -mainItem.height; openAnimation.to = openAnimation.topOpenPosition; openAnimation.running = true; root.sheetOpen = true; mainItem.visible = true; } function close() { if (scrollView.flickableItem.contentY < 0) { closeAnimation.to = -height; } else { closeAnimation.to = scrollView.flickableItem.contentHeight; } closeAnimation.running = true; } onBackgroundChanged: { background.parent = flickableContents; background.z = -1; } onContentItemChanged: { if (contentItem.hasOwnProperty("contentY") && // Check if flickable contentItem.hasOwnProperty("contentHeight")) { contentItem.parent = scrollView; scrollView.contentItem = contentItem; } else { contentItem.parent = contentItemParent; scrollView.contentItem = flickableContents; contentItem.anchors.left = contentItemParent.left; contentItem.anchors.right = contentItemParent.right; } scrollView.flickableItem.flickableDirection = Flickable.VerticalFlick; } onSheetOpenChanged: { if (sheetOpen) { open(); } else { close(); Qt.inputMethod.hide(); } } onHeaderChanged: { header.parent = headerParent; header.anchors.fill = headerParent; //TODO: special case for actual ListViews } onFooterChanged: { footer.parent = footerParent; footer.anchors.fill = footerParent; } Component.onCompleted: { if (!root.parent && typeof applicationWindow !== "undefined") { root.parent = applicationWindow().overlay } } readonly property Item rootItem: MouseArea { id: mainItem Theme.colorSet: root.Theme.colorSet Theme.inherit: root.Theme.inherit //we want to be over any possible OverlayDrawers, including handles parent: root.parent anchors.fill: parent - z: root.parent == typeof applicationWindow !== "undefined" && applicationWindow().overlay ? 0 : 2000000 + z: typeof applicationWindow !== "undefined" && root.parent == applicationWindow().overlay ? 0 : 2000000 visible: false drag.filterChildren: true hoverEnabled: true onClicked: { var pos = mapToItem(flickableContents, mouse.x, mouse.y); if (!flickableContents.contains(pos)) { root.close(); } } readonly property int contentItemPreferredWidth: root.contentItem.Layout.preferredWidth > 0 ? root.contentItem.Layout.preferredWidth : root.contentItem.implicitWidth onWidthChanged: { if (!contentItem.contentItem) return var width = Math.max(mainItem.width/2, Math.min(mainItem.width, mainItem.contentItemPreferredWidth)); contentItem.contentItem.x = (mainItem.width - width)/2 contentItem.contentItem.width = width; } onHeightChanged: { var focusItem; focusItem = Window.activeFocusItem; if (!focusItem) { return; } //NOTE: there is no function to know if an item is descended from another, //so we have to walk the parent hierarchy by hand var isDescendent = false; var candidate = focusItem.parent; while (candidate) { if (candidate == root) { isDescendent = true; break; } candidate = candidate.parent; } if (!isDescendent) { return; } var cursorY = 0; if (focusItem.cursorPosition !== undefined) { cursorY = focusItem.positionToRectangle(focusItem.cursorPosition).y; } var pos = focusItem.mapToItem(flickableContents, 0, cursorY - Units.gridUnit*3); //focused item already visible? add some margin for the space of the action buttons if (pos.y >= scrollView.flickableItem.contentY && pos.y <= scrollView.flickableItem.contentY + scrollView.flickableItem.height - Units.gridUnit * 8) { return; } scrollView.flickableItem.contentY = pos.y; } ParallelAnimation { id: openAnimation property int margins: Units.gridUnit * 5 property int topOpenPosition: Math.min(-mainItem.height*0.15, scrollView.flickableItem.contentHeight - mainItem.height + margins) property alias from: openAnimationInternal.from property alias to: openAnimationInternal.to NumberAnimation { id: openAnimationInternal target: scrollView.flickableItem properties: "contentY" from: -mainItem.height to: openAnimation.topOpenPosition duration: Units.longDuration easing.type: Easing.OutQuad onRunningChanged: { //HACK to center listviews if (!running && contentItem.contentItem) { var width = Math.max(mainItem.width/2, Math.min(mainItem.width, mainItem.contentItemPreferredWidth)); contentItem.contentItem.x = (mainItem.width - width)/2 contentItem.contentItem.width = width; } } } OpacityAnimator { target: mainItem from: 0 to: 1 duration: Units.longDuration easing.type: Easing.InQuad } } SequentialAnimation { id: closeAnimation property int to: -mainItem.height ParallelAnimation { NumberAnimation { target: scrollView.flickableItem properties: "contentY" to: closeAnimation.to duration: Units.longDuration easing.type: Easing.InQuad } OpacityAnimator { target: mainItem from: 1 to: 0 duration: Units.longDuration easing.type: Easing.InQuad } } ScriptAction { script: { scrollView.flickableItem.contentY = -mainItem.height; mainItem.visible = root.sheetOpen = false; } } } Rectangle { anchors.fill: parent color: Theme.textColor opacity: 0.6 * Math.min( (Math.min(scrollView.flickableItem.contentY + scrollView.flickableItem.height, scrollView.flickableItem.height) / scrollView.flickableItem.height), (2 + (scrollView.flickableItem.contentHeight - scrollView.flickableItem.contentY - scrollView.flickableItem.topMargin - scrollView.flickableItem.bottomMargin)/scrollView.flickableItem.height)) } Icon { id: closeIcon anchors { right: headerItem.right margins: Units.smallSpacing top: headerItem.top } z: 3 visible: !Settings.isMobile width: Units.iconSizes.smallMedium height: width source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic" active: closeMouseArea.containsMouse MouseArea { id: closeMouseArea hoverEnabled: true anchors.fill: parent onClicked: root.close(); } } Rectangle { id: headerItem width: flickableContents.width x: flickableContents.x visible: root.header height: Math.max(headerParent.implicitHeight, closeIcon.height) + Units.smallSpacing * 2 color: Theme.backgroundColor //different y depending if we're a listview or a normal item y: Math.max(0, -scrollView.flickableItem.contentY - (scrollView.contentItem != flickableContents ? height : 0)) z: 2 Item { id: headerParent implicitHeight: header ? header.implicitHeight : 0 anchors { fill: parent margins: Units.smallSpacing rightMargin: closeIcon.width + Units.smallSpacing } } EdgeShadow { z: -2 edge: Qt.TopEdge anchors { right: parent.right left: parent.left top: parent.bottom } opacity: parent.y == 0 ? 1 : 0 Behavior on opacity { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } } Rectangle { id: footerItem width: flickableContents.width x: flickableContents.x visible: root.footer height: footerParent.implicitHeight + Units.smallSpacing * 2 + extraMargin color: Theme.backgroundColor y: mainItem.mapFromItem(flickableContents, 0, flickableContents.height).y - height onHeightChanged: y = Math.min(mainItem.height, mainItem.mapFromItem(flickableContents, 0, flickableContents.height).y) - footerItem.height; //Show an extra margin when: //* the application is in mobile mode (no toolbarapplicationheader) //* the bottom screen controls are visible //* the sheet is displayed *under* the controls property int extraMargin: (!root.parent || typeof applicationWindow === "undefined" || (root.parent === applicationWindow().overlay) || !applicationWindow().controlsVisible || (applicationWindow().pageStack && applicationWindow().pageStack.globalToolBar && applicationWindow().pageStack.globalToolBar.actualStyle == ApplicationHeaderStyle.ToolBar) || (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0)) ? 0 : Units.gridUnit * 3 Connections { target: scrollView.flickableItem onContentYChanged: footerItem.y = Math.min(mainItem.height, mainItem.mapFromItem(flickableContents, 0, flickableContents.height).y) - footerItem.height; onHeightChanged: scrollView.flickableItem.contentYChanged() } z: 2 Item { id: footerParent implicitHeight: footer ? footer.implicitHeight : 0 anchors { top: parent.top left: parent.left right: parent.right margins: Units.smallSpacing } } EdgeShadow { z: -2 edge: Qt.BottomEdge anchors { right: parent.right left: parent.left bottom: parent.top } opacity: parent.y + parent.height < mainItem.height ? 0 : 1 Behavior on opacity { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } } Item { id: flickableContents //anchors.horizontalCenter: parent.horizontalCenter x: (mainItem.width - width) / 2 readonly property real listHeaderHeight: scrollView.flickableItem && root.contentItem.headerItem ? root.contentItem.headerItem.height : 0 y: (scrollView.contentItem != flickableContents ? -scrollView.flickableItem.contentY - listHeaderHeight - (headerItem.visible ? headerItem.height : 0): 0) width: mainItem.contentItemPreferredWidth <= 0 ? mainItem.width : Math.max(mainItem.width/2, Math.min(mainItem.width, mainItem.contentItemPreferredWidth)) height: (scrollView.contentItem != flickableContents ? scrollView.flickableItem.contentHeight + listHeaderHeight : (root.contentItem.height + topPadding + bottomPadding)) + (headerItem.visible ? headerItem.height : 0) + (footerItem.visible ? footerItem.height : 0) Item { id: contentItemParent anchors { fill: parent leftMargin: leftPadding topMargin: topPadding + (headerItem.visible ? headerItem.height : 0) rightMargin: rightPadding bottomMargin: bottomPadding + (footerItem.visible ? footerItem.height : 0) } } } Binding { when: scrollView.flickableItem != null target: scrollView.flickableItem property: "topMargin" //hack needed for smoother open anim value: openAnimation.running ? -scrollView.flickableItem.contentY : -openAnimation.topOpenPosition } Binding { when: scrollView.flickableItem != null target: scrollView.flickableItem property: "bottomMargin" value: openAnimation.margins } Binding { target: scrollView.verticalScrollBar ? scrollView.verticalScrollBar.anchors : null property: "topMargin" value: headerItem.y + headerItem.height } Binding { target: scrollView.verticalScrollBar property: "height" value: mainItem.height - (scrollView.verticalScrollBar ? scrollView.verticalScrollBar.anchors.topMargin : 0) - (mainItem.height - footerItem.y) } Binding { target: scrollView.verticalScrollBar ? scrollView.verticalScrollBar.anchors : null property: "rightMargin" value: mainItem.width - flickableContents.width - flickableContents.x } Connections { target: scrollView.flickableItem onContentHeightChanged: { if (openAnimation.running) { openAnimation.running = false; open(); } } onDraggingChanged: { if (scrollView.flickableItem.dragging) { return; } //close if ((mainItem.height + scrollView.flickableItem.contentY) < mainItem.height/2) { closeAnimation.to = -mainItem.height; closeAnimation.running = true; } else if ((mainItem.height*0.6 + scrollView.flickableItem.contentY) > scrollView.flickableItem.contentHeight) { closeAnimation.to = scrollView.flickableItem.contentHeight; closeAnimation.running = true; } } } Binding { target: scrollView.verticalScrollBar property: "visible" value: scrollView.flickableItem.contentHeight > mainItem.height*0.8 } Connections { target: scrollView.verticalScrollBar onActiveChanged: { if (!scrollView.verticalScrollBar.active) { scrollView.flickableItem.movementEnded(); } } } ScrollView { id: scrollView anchors.fill: parent horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff } } }