diff --git a/src/controls/Page.qml b/src/controls/Page.qml index 08df4328..7a75a2aa 100644 --- a/src/controls/Page.qml +++ b/src/controls/Page.qml @@ -1,267 +1,269 @@ /* * 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.2 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.2 as Kirigami * * Kirigami.ApplicationWindow { * [...] * contextDrawer: Kirigami.ContextDrawer { * id: contextDrawer * } * [...] * } * @endcode * * @code * import org.kde.kirigami 2.2 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.2 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.2 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.2 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.2 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: applicationWindow().pageStack.layers.depth > 1 - ? applicationWindow().pageStack.layers.currentItem == root - : applicationWindow().pageStack.currentItem == root + readonly property bool isCurrentPage: typeof applicationWindow === "undefined" + ? true + : (applicationWindow().pageStack.layers.depth > 1 + ? applicationWindow().pageStack.layers.currentItem == root + : applicationWindow().pageStack.currentItem == root) PageActionPropertyGroup { id: actionsGroup } /** * 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 } } } } //on material the shadow would bleed over clip: header !== undefined 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 - source: (applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0) || - (applicationWindow().footer && applicationWindow().footer.visible && applicationWindow().footer.toString().indexOf("ToolBarApplicationHeader") === 0) + source: typeof applicationWindow !== "undefined" && ((applicationWindow().header && applicationWindow().header.toString().indexOf("ToolBarApplicationHeader") === 0) || + (applicationWindow().footer && applicationWindow().footer.visible && applicationWindow().footer.toString().indexOf("ToolBarApplicationHeader") === 0)) ? "" : Qt.resolvedUrl("./private/ActionButton.qml") } Layout.fillWidth: true } diff --git a/src/controls/private/ActionButton.qml b/src/controls/private/ActionButton.qml index 8b6a731f..d78872d8 100644 --- a/src/controls/private/ActionButton.qml +++ b/src/controls/private/ActionButton.qml @@ -1,467 +1,471 @@ /* * 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 QtQuick.Controls 2.0 as Controls import QtGraphicalEffects 1.0 import org.kde.kirigami 2.2 import "../templates/private" Item { id: root Theme.colorSet: Theme.Button Theme.inherit: false anchors { left: parent.left right: parent.right bottom: parent.bottom bottomMargin: root.page.footer ? root.page.footer.height : 0 } //smallSpacing for the shadow height: button.height + Units.smallSpacing clip: true readonly property Page page: root.parent.page //either Action or QAction should work here readonly property QtObject action: root.page && root.page.mainAction && root.page.mainAction.enabled ? root.page.mainAction : null readonly property QtObject leftAction: root.page && root.page.leftAction && root.page.leftAction.enabled ? root.page.leftAction : null readonly property QtObject rightAction: root.page && root.page.rightAction && root.page.rightAction.enabled ? root.page.rightAction : null + readonly property bool hasApplicationWindow: typeof applicationWindow !== "undefined" && applicationWindow + readonly property bool hasGlobalDrawer: typeof globalDrawer !== "undefined" && globalDrawer + readonly property bool hasContextDrawer: typeof contextDrawer !== "undefined" && contextDrawer + transform: Translate { id: translateTransform y: mouseArea.internalVisibility ? 0 : button.height Behavior on y { NumberAnimation { duration: Units.longDuration easing.type: mouseArea.internalVisibility == true ? Easing.InQuad : Easing.OutQuad } } } onWidthChanged: button.x = root.width/2 - button.width/2 Item { id: button x: root.width/2 - button.width/2 anchors { bottom: parent.bottom bottomMargin: Units.smallSpacing } implicitWidth: implicitHeight + Units.iconSizes.smallMedium*2 + Units.gridUnit implicitHeight: Units.iconSizes.medium + Units.largeSpacing * 2 onXChanged: { if (mouseArea.pressed || edgeMouseArea.pressed || fakeContextMenuButton.pressed) { if (globalDrawer && globalDrawer.enabled && globalDrawer.modal) { globalDrawer.peeking = true; globalDrawer.visible = true; globalDrawer.position = Math.min(1, Math.max(0, (x - root.width/2 + button.width/2)/globalDrawer.contentItem.width + mouseArea.drawerShowAdjust)); } if (contextDrawer && contextDrawer.enabled && contextDrawer.modal) { contextDrawer.peeking = true; contextDrawer.visible = true; contextDrawer.position = Math.min(1, Math.max(0, (root.width/2 - button.width/2 - x)/contextDrawer.contentItem.width + mouseArea.drawerShowAdjust)); } } } MouseArea { id: mouseArea anchors.fill: parent visible: action != null || leftAction != null || rightAction != null - property bool internalVisibility: (applicationWindow === undefined || (applicationWindow().controlsVisible && applicationWindow().height > root.height*2)) && (root.action === null || root.action.visible === undefined || root.action.visible) + property bool internalVisibility: (!root.hasApplicationWindow || (applicationWindow().controlsVisible && applicationWindow().height > root.height*2)) && (root.action === null || root.action.visible === undefined || root.action.visible) preventStealing: true drag { target: button //filterChildren: true axis: Drag.XAxis - minimumX: contextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 - maximumX: globalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 + minimumX: root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 + maximumX: root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 } property var downTimestamp; property int startX property int startMouseY property real drawerShowAdjust readonly property int currentThird: (3*mouseX)/width readonly property QtObject actionUnderMouse: { switch(currentThird) { case 0: return leftAction; case 1: return action; case 2: return rightAction; default: return null } } hoverEnabled: true Controls.ToolTip.visible: containsMouse && !Settings.isMobile && actionUnderMouse Controls.ToolTip.text: actionUnderMouse ? actionUnderMouse.text : "" Controls.ToolTip.delay: Units.toolTipDelay onPressed: { //search if we have a page to set to current if (applicationWindow !== undefined && applicationWindow().pageStack.currentIndex !== undefined && root.page.parent.level !== undefined) { //search the button parent's parent, that is the page parent //this will make the context drawer open for the proper page applicationWindow().pageStack.currentIndex = root.page.parent.level; } downTimestamp = (new Date()).getTime(); startX = button.x + button.width/2; startMouseY = mouse.y; drawerShowAdjust = 0; } onReleased: { if (globalDrawer) globalDrawer.peeking = false; if (contextDrawer) contextDrawer.peeking = false; //pixel/second var x = button.x + button.width/2; var speed = ((x - startX) / ((new Date()).getTime() - downTimestamp) * 1000); drawerShowAdjust = 0; //project where it would be a full second in the future if (globalDrawer && globalDrawer.modal && x + speed > Math.min(root.width/4*3, root.width/2 + globalDrawer.contentItem.width/2)) { globalDrawer.open(); contextDrawer.close(); } else if (contextDrawer && x + speed < Math.max(root.width/4, root.width/2 - contextDrawer.contentItem.width/2)) { if (contextDrawer && contextDrawer.modal) { contextDrawer.open(); } if (globalDrawer && globalDrawer.modal) { globalDrawer.close(); } } else { if (globalDrawer && globalDrawer.modal) { globalDrawer.close(); } if (contextDrawer && contextDrawer.modal) { contextDrawer.close(); } } //Don't rely on native onClicked, but fake it here: //Qt.startDragDistance is not adapted to devices dpi in case //of Android, so consider the button "clicked" when: //*the button has been dragged less than a gridunit //*the finger is still on the button if (Math.abs((button.x + button.width/2) - startX) < Units.gridUnit && mouse.y > 0) { if (!actionUnderMouse) { return; } //if an action has been assigned, trigger it if (actionUnderMouse && actionUnderMouse.trigger) { actionUnderMouse.trigger(); } } } onPositionChanged: { drawerShowAdjust = Math.min(0.3, Math.max(0, (startMouseY - mouse.y)/(Units.gridUnit*15))); button.xChanged(); } onPressAndHold: { if (!actionUnderMouse) { return; } //if an action has been assigned, show a message like a tooltip if (actionUnderMouse && actionUnderMouse.text && Settings.isMobile) { Controls.ToolTip.show(actionUnderMouse.text, 3000) } } Connections { - target: globalDrawer + target: root.hasGlobalDrawer ? globalDrawer : null onPositionChanged: { if ( globalDrawer && globalDrawer.modal && !mouseArea.pressed && !edgeMouseArea.pressed && !fakeContextMenuButton.pressed) { button.x = globalDrawer.contentItem.width * globalDrawer.position + root.width/2 - button.width/2; } } } Connections { - target: contextDrawer + target: root.hasContextDrawer ? globalDrawer : null onPositionChanged: { if (contextDrawer && contextDrawer.modal && !mouseArea.pressed && !edgeMouseArea.pressed && !fakeContextMenuButton.pressed) { button.x = root.width/2 - button.width/2 - contextDrawer.contentItem.width * contextDrawer.position; } } } Item { id: background anchors { fill: parent } Rectangle { id: buttonGraphics radius: width/2 anchors.centerIn: parent height: parent.height - Units.smallSpacing*2 width: height visible: root.action readonly property bool pressed: root.action && ((root.action == mouseArea.actionUnderMouse && mouseArea.pressed) || root.action.checked) property color baseColor: root.action && root.action.icon && root.action.icon.color && root.action.icon.color != undefined && root.action.icon.color.a > 0 ? root.action.icon.color : Theme.highlightColor color: pressed ? Qt.darker(baseColor, 1.3) : baseColor Icon { id: icon anchors.centerIn: parent width: Units.iconSizes.smallMedium height: width source: root.action && root.action.iconName ? root.action.iconName : "" selected: true } Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Behavior on x { NumberAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } //left button Rectangle { id: leftButtonGraphics z: -1 anchors { left: parent.left //verticalCenter: parent.verticalCenter bottom: parent.bottom bottomMargin: Units.smallSpacing } radius: Units.devicePixelRatio*2 height: Units.iconSizes.smallMedium + Units.smallSpacing * 2 width: height + (root.action ? Units.gridUnit*2 : 0) visible: root.leftAction readonly property bool pressed: root.leftAction && ((mouseArea.actionUnderMouse == root.leftAction && mouseArea.pressed) || root.leftAction.checked) property color baseColor: root.leftAction && root.leftAction.icon && root.leftAction.icon.color && root.leftAction.icon.color != undefined && root.leftAction.icon.color.a > 0 ? root.leftAction.icon.color : Theme.highlightColor color: pressed ? baseColor : Theme.backgroundColor Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Icon { source: root.leftAction && root.leftAction.iconName ? root.leftAction.iconName : "" width: Units.iconSizes.smallMedium height: width selected: leftButtonGraphics.pressed anchors { left: parent.left verticalCenter: parent.verticalCenter margins: Units.smallSpacing * 2 } } } //right button Rectangle { id: rightButtonGraphics z: -1 anchors { right: parent.right //verticalCenter: parent.verticalCenter bottom: parent.bottom bottomMargin: Units.smallSpacing } radius: Units.devicePixelRatio*2 height: Units.iconSizes.smallMedium + Units.smallSpacing * 2 width: height + (root.action ? Units.gridUnit*2 : 0) visible: root.rightAction readonly property bool pressed: root.rightAction && ((mouseArea.actionUnderMouse == root.rightAction && mouseArea.pressed) || root.rightAction.checked) property color baseColor: root.rightAction && root.rightAction.icon && root.rightAction.icon.color && root.rightAction.icon.color != undefined && root.rightAction.icon.color.a > 0 ? root.rightAction.icon.color : Theme.highlightColor color: pressed ? baseColor : Theme.backgroundColor Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } Icon { source: root.rightAction && root.rightAction.iconName ? root.rightAction.iconName : "" width: Units.iconSizes.smallMedium height: width selected: rightButtonGraphics.pressed anchors { right: parent.right verticalCenter: parent.verticalCenter margins: Units.smallSpacing * 2 } } } } DropShadow { anchors.fill: background horizontalOffset: 0 verticalOffset: Units.devicePixelRatio radius: Units.gridUnit /2 samples: 16 color: Qt.rgba(0, 0, 0, mouseArea.pressed ? 0.6 : 0.4) source: background } } } MouseArea { id: fakeContextMenuButton anchors { right: edgeMouseArea.right bottom: edgeMouseArea.bottom } drag { target: button axis: Drag.XAxis - minimumX: contextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 - maximumX: globalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 + minimumX: root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 + maximumX: root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 } visible: root.page.actions && root.page.actions.contextualActions.length > 0 && (applicationWindow === undefined || applicationWindow().wideScreen) //using internal pagerow api && (root.page && root.page.parent ? root.page.parent.level < applicationWindow().pageStack.depth-1 : false) width: Units.iconSizes.medium + Units.smallSpacing*2 height: width Item { anchors { fill:parent margins: -Units.gridUnit } DropShadow { anchors.fill: handleGraphics horizontalOffset: 0 verticalOffset: Units.devicePixelRatio radius: Units.gridUnit /2 samples: 16 color: Qt.rgba(0, 0, 0, fakeContextMenuButton.pressed ? 0.6 : 0.4) source: handleGraphics } Rectangle { id: handleGraphics anchors.centerIn: parent color: fakeContextMenuButton.pressed ? Theme.highlightColor : Theme.backgroundColor width: Units.iconSizes.smallMedium + Units.smallSpacing * 2 height: width radius: Units.devicePixelRatio Icon { anchors.centerIn: parent width: Units.iconSizes.smallMedium selected: fakeContextMenuButton.pressed height: width source: "overflow-menu" } Behavior on color { ColorAnimation { duration: Units.longDuration easing.type: Easing.InOutQuad } } } } onPressed: { mouseArea.onPressed(mouse) } onReleased: { if (globalDrawer) { globalDrawer.peeking = false; } if (contextDrawer) { contextDrawer.peeking = false; } var pos = root.mapFromItem(fakeContextMenuButton, mouse.x, mouse.y); if (contextDrawer) { if (pos.x < root.width/2) { contextDrawer.open(); } else if (contextDrawer.drawerOpen && mouse.x > 0 && mouse.x < width) { contextDrawer.close(); } } if (globalDrawer) { if (globalDrawer.position > 0.5) { globalDrawer.open(); } else { globalDrawer.close(); } } if (containsMouse && (!globalDrawer || !globalDrawer.drawerOpen || !globalDrawer.modal) && (!contextDrawer || !contextDrawer.drawerOpen || !contextDrawer.modal)) { contextMenu.visible = !contextMenu.visible; } } Controls.Menu { id: contextMenu x: parent.width - width y: -height Repeater { model: root.page.actions.contextualActions delegate: BasicListItem { text: model.text icon: model.iconName backgroundColor: "transparent" visible: model.visible enabled: modelData.enabled checkable: modelData.checkable checked: modelData.checked separatorVisible: false onClicked: { modelData.trigger(); contextMenu.visible = false; } } } } } MouseArea { id: edgeMouseArea z:99 anchors { left: parent.left right: parent.right bottom: parent.bottom } drag { target: button //filterChildren: true axis: Drag.XAxis - minimumX: contextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 - maximumX: globalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 + minimumX: root.hasContextDrawer && contextDrawer.enabled && contextDrawer.modal ? 0 : root.width/2 - button.width/2 + maximumX: root.hasGlobalDrawer && globalDrawer.enabled && globalDrawer.modal ? root.width : root.width/2 - button.width/2 } height: Units.smallSpacing * 3 onPressed: mouseArea.onPressed(mouse) onPositionChanged: mouseArea.positionChanged(mouse) onReleased: mouseArea.released(mouse) } } diff --git a/src/controls/private/RefreshableScrollView.qml b/src/controls/private/RefreshableScrollView.qml index 243d0852..ada7324b 100644 --- a/src/controls/private/RefreshableScrollView.qml +++ b/src/controls/private/RefreshableScrollView.qml @@ -1,287 +1,288 @@ /* * 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 QtQuick.Window 2.2 import QtQuick.Controls 2.0 as QQC2 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.2 import org.kde.kirigami 2.2 import "../templates/private" as P /** * RefreshableScrollView is a scroll view for any Flickable that supports the * "scroll down to refresh" behavior, and also allows 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. * * Example usage: * * @code * import org.kde.kirigami 2.2 as Kirigami * [...] * * Kirigami.RefreshableScrollView { * 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 * */ P.ScrollView { id: root /** * type: 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 bool refreshing: false /** * type: bool * If true the list supports the "pull down to refresh" behavior. */ property bool supportsRefreshing: false /** * 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 /** * Set when this scrollview manages a whole page */ property Page page property Item _swipeFilter children: [ Item { id: busyIndicatorFrame z: 99 y: root.flickableItem.verticalLayoutDirection === ListView.BottomToTop ? -root.flickableItem.contentY+height : -root.flickableItem.contentY-height width: root.flickableItem.width height: busyIndicator.height + Units.gridUnit * 2 QQC2.BusyIndicator { id: busyIndicator anchors.centerIn: parent running: root.refreshing visible: root.refreshing //Android busywidget QQC seems to be broken at custom sizes } property int headerItemHeight: (root.flickableItem.headerItem ? (root.flickableItem.headerItem.maximumHeight ? root.flickableItem.headerItem.maximumHeight : root.flickableItem.headerItem.height) : 0) Rectangle { id: spinnerProgress anchors { fill: busyIndicator margins: Math.ceil(Units.smallSpacing/2) } radius: width visible: supportsRefreshing && !refreshing && progress > 0 color: "transparent" opacity: 0.8 border.color: Theme.backgroundColor border.width: Math.ceil(Units.smallSpacing/4) //also take into account the listview header height if present property real progress: supportsRefreshing && !refreshing ? ((parent.y - busyIndicatorFrame.headerItemHeight)/busyIndicatorFrame.height) : 0 } ConicalGradient { source: spinnerProgress visible: spinnerProgress.visible anchors.fill: spinnerProgress gradient: Gradient { GradientStop { position: 0.00; color: Theme.highlightColor } GradientStop { position: spinnerProgress.progress; color: Theme.highlightColor } GradientStop { position: spinnerProgress.progress + 0.01; color: "transparent" } GradientStop { position: 1.00; color: "transparent" } } } onYChanged: { //it's overshooting enough and not reachable: start countdown for reachability if (y - busyIndicatorFrame.headerItemHeight > root.topPadding + Units.gridUnit && !applicationWindow().reachableMode) { overshootResetTimer.running = true; //not reachable and not overshooting enough, stop reachability countdown } else if (typeof(applicationWindow) == "undefined" || !applicationWindow().reachableMode) { //it's important it doesn't restart overshootResetTimer.running = false; } if (!supportsRefreshing) { return; } //also take into account the listview header height if present if (!root.refreshing && y - busyIndicatorFrame.headerItemHeight > busyIndicatorFrame.height/2 + topPadding) { refreshTriggerTimer.running = true; } else { refreshTriggerTimer.running = false; } } Timer { id: refreshTriggerTimer interval: 500 onTriggered: { //also take into account the listview header height if present if (!root.refreshing && parent.y - busyIndicatorFrame.headerItemHeight > busyIndicatorFrame.height/2 + topPadding) { root.refreshing = true; } } } Connections { - target: applicationWindow() + enabled: typeof applicationWindow !== "undefined" + target: typeof applicationWindow !== "undefined" ? applicationWindow() : null onReachableModeChanged: { overshootResetTimer.running = applicationWindow().reachableMode; } } Timer { id: overshootResetTimer - interval: applicationWindow().reachableMode ? 8000 : 2000 + interval: (typeof applicationWindow !== "undefined" && applicationWindow().reachableMode) ? 8000 : 2000 onTriggered: { //put it there because widescreen may have changed since timer start - if (!Settings.isMobile || applicationWindow().wideScreen || root.flickableItem.verticalLayoutDirection === ListView.BottomToTop) { + if (!Settings.isMobile || (typeof applicationWindow !== "undefined" && applicationWindow().wideScreen) || root.flickableItem.verticalLayoutDirection === ListView.BottomToTop) { return; } applicationWindow().reachableMode = !applicationWindow().reachableMode; } } Binding { target: root.flickableItem property: "flickableDirection" value: Flickable.VerticalFlick } Binding { target: root.flickableItem property: "bottomMargin" value: root.page.bottomPadding } Binding { target: root.contentItem property: "width" value: root.flickableItem.width } } ] onHeightChanged: { if (!Window.window) { return; } var focusItem = Window.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 hyerarchy 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(root.contentItem, 0, cursorY); //focused item alreqady visible? add some margin for the space of the action buttons if (pos.y >= root.flickableItem.contentY && pos.y <= root.flickableItem.contentY + root.flickableItem.height - Units.gridUnit * 8) { return; } root.flickableItem.contentY = pos.y; } onLeftPaddingChanged: { //for gridviews do apply margins if (root.contentItem == root.flickableItem) { if (typeof root.flickableItem.cellWidth != "undefined") { flickableItem.anchors.leftMargin = leftPadding; flickableItem.anchors.rightMargin = rightPadding; } else { flickableItem.anchors.leftMargin = 0; flickableItem.anchors.rightMargin = 0; } flickableItem.anchors.rightMargin = 0; flickableItem.anchors.bottomMargin = 0; } else { flickableItem.anchors.leftMargin = leftPadding; flickableItem.anchors.topMargin = topPadding; flickableItem.anchors.rightMargin = rightPadding; flickableItem.anchors.bottomMargin = bottomPadding; } } }