diff --git a/components/mobilehomescreencomponents/qml/DrawerDelegate.qml b/components/mobilehomescreencomponents/qml/DrawerDelegate.qml deleted file mode 100644 index d50d9b26..00000000 --- a/components/mobilehomescreencomponents/qml/DrawerDelegate.qml +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2019 Marco Martin - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -import QtQuick 2.4 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 2.3 as Controls -import QtGraphicalEffects 1.6 - -import org.kde.plasma.plasmoid 2.0 -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 3.0 as PlasmaComponents -import org.kde.kquickcontrolsaddons 2.0 - -import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager -import org.kde.plasma.private.mobileshell 1.0 as MobileShell - -MouseArea { - id: delegate - width: GridView.view.cellWidth - height: GridView.view.cellHeight - - property int reservedSpaceForLabel - property alias iconItem: icon - - signal launch(int x, int y, var source, string title, string storageId) - signal dragStarted(string imageSource, int x, int y, string mimeData) - - onPressAndHold: { - delegate.grabToImage(function(result) { - delegate.Drag.imageSource = result.url - dragStarted(result.url, width/2, height/2, model.applicationStorageId) - }) - } - - propagateComposedEvents: true - onClicked: { - mouse.accepted = true - if (model.applicationRunning) { - delegate.launch(0, 0, "", model.applicationName, model.applicationStorageId); - } else { - delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, model.applicationName, model.applicationStorageId); - } - } - - //preventStealing: true - ColumnLayout { - anchors { - fill: parent - leftMargin: PlasmaCore.Units.smallSpacing * 2 - topMargin: PlasmaCore.Units.smallSpacing * 2 - rightMargin: PlasmaCore.Units.smallSpacing * 2 - bottomMargin: PlasmaCore.Units.smallSpacing * 2 - } - spacing: 0 - - PlasmaCore.IconItem { - id: icon - - Layout.alignment: Qt.AlignHCenter | Qt.AlignTop - Layout.fillWidth: true - Layout.minimumHeight: parent.height - delegate.reservedSpaceForLabel - Layout.preferredHeight: Layout.minimumHeight - - usesPlasmaTheme: false - source: model.applicationIcon - - Rectangle { - anchors { - horizontalCenter: parent.horizontalCenter - bottom: parent.bottom - } - visible: model.applicationRunning - radius: width - width: PlasmaCore.Units.smallSpacing - height: width - color: PlasmaCore.Theme.highlightColor - } - } - - PlasmaComponents.Label { - id: label - visible: text.length > 0 - - Layout.fillWidth: true - Layout.preferredHeight: delegate.reservedSpaceForLabel - wrapMode: Text.WordWrap - Layout.leftMargin: -parent.anchors.leftMargin + PlasmaCore.Units.smallSpacing - Layout.rightMargin: -parent.anchors.rightMargin + PlasmaCore.Units.smallSpacing - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignTop - maximumLineCount: 2 - elide: Text.ElideRight - - text: model.applicationName - - //FIXME: export smallestReadableFont - font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9 - color: "white" - } - } -} - diff --git a/components/mobilehomescreencomponents/qml/FlickContainer.qml b/components/mobilehomescreencomponents/qml/FlickContainer.qml new file mode 100644 index 00000000..24dc526f --- /dev/null +++ b/components/mobilehomescreencomponents/qml/FlickContainer.qml @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.15 + +import org.kde.taskmanager 0.1 as TaskManager +import org.kde.plasma.core 2.1 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.plasma.private.nanoshell 2.0 as NanoShell +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +Flickable { + id: root + + required property var homeScreenState + + // we use flickable solely for capturing flicks, not positioning elements + contentWidth: width + 99999 + contentHeight: height + 99999 + contentX: startContentX + contentY: startContentY + + readonly property real startContentX: contentWidth / 2 + readonly property real startContentY: contentHeight / 2 + + property bool positionChangedDueToFlickable: false + + // ensure that flickable is not moving when other sources are changing position + Connections { + target: root.homeScreenState + + onXPositionChanged: { + if (!root.positionChangedDueToFlickable) { + root.cancelMovement(); + } + root.positionChangedDueToFlickable = true; + } + onYPositionChanged: { + if (!root.positionChangedDueToFlickable) { + root.cancelMovement(); + } + root.positionChangedDueToFlickable = true; + } + } + + // update position from flickable movement + property real oldContentX + property real oldContentY + onContentXChanged: { + positionChangedDueToFlickable = true; + homeScreenState.updatePositionWithOffset(contentX - oldContentX, 0); + oldContentX = contentX; + } + onContentYChanged: { + positionChangedDueToFlickable = true; + homeScreenState.updatePositionWithOffset(0, -(contentY - oldContentY)); + oldContentY = contentY; + } + + onMovementStarted: homeScreenState.cancelAnimations(); + onMovementEnded: { + if (!homeScreenState.animationsRunning) { + homeScreenState.updateState(); + } + resetPosition(); + } + onFlickEnded: { + homeScreenState.cancelEditModeForItemsRequested() + resetPosition(); + } + + onDragStarted: homeScreenState.cancelEditModeForItemsRequested() + onDragEnded: homeScreenState.cancelEditModeForItemsRequested() + onFlickStarted: homeScreenState.cancelEditModeForItemsRequested() + + onDraggingChanged: { + if (!dragging) { + cancelMovement(); + resetPosition(); + if (!homeScreenState.animationsRunning) { + homeScreenState.updateState(); + } + } else { + homeScreenState.cancelAnimations(); + } + } + + function cancelMovement() { + root.cancelFlick(); + + // HACK: cancelFlick() doesn't seem to cancel flicks... + root.flick(-horizontalVelocity, -verticalVelocity); + } + + function resetPosition() { + positionChangedDueToFlickable = true; + oldContentX = startContentX; + contentX = startContentX; + oldContentY = startContentY; + contentY = startContentY; + } +} + diff --git a/components/mobilehomescreencomponents/qml/HomeDelegate.qml b/components/mobilehomescreencomponents/qml/HomeDelegate.qml index 328686cd..00586c95 100644 --- a/components/mobilehomescreencomponents/qml/HomeDelegate.qml +++ b/components/mobilehomescreencomponents/qml/HomeDelegate.qml @@ -23,6 +23,8 @@ import "private" as Private ContainmentLayoutManager.ItemContainer { id: delegate + property var homeScreenState + z: dragActive ? 1 : 0 property var modelData: typeof model !== "undefined" ? model : null @@ -64,11 +66,11 @@ ContainmentLayoutManager.ItemContainer { } } Connections { - target: mainFlickable + target: homeScreenState function onCancelEditModeForItemsRequested() { cancelEdit() } - function onContentYChanged() { + function onXPositionChanged() { syncDelegateGeometry() } } diff --git a/components/mobilehomescreencomponents/qml/HomeScreen.qml b/components/mobilehomescreencomponents/qml/HomeScreen.qml new file mode 100644 index 00000000..b1d08dcf --- /dev/null +++ b/components/mobilehomescreencomponents/qml/HomeScreen.qml @@ -0,0 +1,135 @@ +/* + * SPDX-FileCopyrightText: 2019 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents +import org.kde.draganddrop 2.0 as DragDrop + +import "private" as Private +import "appdrawer" + +import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager + +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents + +Item { + id: root + + property bool interactive: true + + property var homeScreenState: HomeScreenState { + totalPagesWidth: pages.contentWidth + + appDrawerFlickable: appDrawer.flickable + + availableScreenHeight: height - (MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0) + availableScreenWidth: width - (MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth) + + appDrawerBottomOffset: favoriteStrip.height + } + + property alias appDrawer: appDrawerLoader.item + property alias homeScreenContents: contents + + Component.onCompleted: homeScreenState.goToPageIndex(0) + + // the parent of the homescreen is a flickable that captures all flicks + FlickContainer { + id: flickContainer + anchors.fill: parent + + homeScreenState: root.homeScreenState + + // disable flick tracking when necessary + interactive: root.interactive && homeScreenState.currentView !== HomeScreenState.AppDrawerView && + root.parent.focus && !contents.appletsLayout.editMode && !plasmoid.editMode && !contents.launcherDragManager.active + + // item is effectively anchored to root, while allowing flickContainer + // to keep track of flicks + Item { + x: flickContainer.contentX + y: flickContainer.contentY + width: flickContainer.width + height: flickContainer.height + + // horizontal pages + HomeScreenPages { + id: pages + homeScreenState: root.homeScreenState + + // account for panels + anchors.fill: parent + anchors.topMargin: MobileShell.TopPanelControls.panelHeight + anchors.rightMargin: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth + anchors.bottomMargin: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 + + // animation when app drawer is being shown + opacity: root.appDrawer ? 1 - root.appDrawer.openFactor : 1 + transform: Translate { + y: root.appDrawer ? (-pages.height / 20) * root.appDrawer.openFactor : 0 + } + + contentWidth: Math.max(width, width * Math.ceil(contents.itemsBoundingRect.width/width)) + (contents.launcherDragManager.active ? width : 0) + showAddPageIndicator: contents.launcherDragManager.active + + HomeScreenContents { + id: contents + homeScreenState: root.homeScreenState + + height: pages.height + width: pages.width * 100 + + favoriteStrip: favoriteStrip + homeScreenPages: pages + } + + footer: FavoriteStrip { + id: favoriteStrip + + appletsLayout: contents.appletsLayout + visible: favoriteStrip.flow.children.length > 0 || contents.launcherDragManager.active || contents.containsDrag + opacity: contents.launcherDragManager.active && HomeScreenComponents.ApplicationListModel.favoriteCount >= HomeScreenComponents.ApplicationListModel.maxFavoriteCount ? 0.3 : 1 + + TapHandler { + target: favoriteStrip + onTapped: { + //Hides icons close button + contents.appletsLayout.appletsLayoutInteracted(); + contents.appletsLayout.editMode = false; + } + onLongPressed: { + if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) { + // only go into edit mode when not in a swipe + contents.appletsLayout.editMode = true; + } + } + onPressedChanged: root.parent.focus = true; + } + } + } + + // app drawer + AppDrawerLoader { + id: appDrawerLoader + anchors.fill: parent + homeScreenState: root.homeScreenState + + // account for panels + topPadding: MobileShell.TopPanelControls.panelHeight + rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth + bottomPadding: MobileShell.TaskPanelControls.isPortrait ? MobileShell.TaskPanelControls.panelHeight : 0 + } + } + } +} diff --git a/components/mobilehomescreencomponents/qml/HomeScreenContents.qml b/components/mobilehomescreencomponents/qml/HomeScreenContents.qml index 94d8857b..bf1766fa 100644 --- a/components/mobilehomescreencomponents/qml/HomeScreenContents.qml +++ b/components/mobilehomescreencomponents/qml/HomeScreenContents.qml @@ -24,10 +24,9 @@ import "private" as Private DragDrop.DropArea { id: dropArea - width: mainFlickable.width * 100 - //width: Math.max(mainFlickable.width, mainFlickable.width * Math.ceil(appletsLayout.childrenRect.width/mainFlickable.width)) - height: mainFlickable.height - + + required property var homeScreenState + property alias launcherDelegate: launcherRepeater.delegate property alias launcherModel: launcherRepeater.model property alias launcherRepeater: launcherRepeater @@ -36,6 +35,7 @@ DragDrop.DropArea { property alias appletsLayout: appletsLayout property FavoriteStrip favoriteStrip + property HomeScreenPages homeScreenPages property LauncherDragManager launcherDragManager: LauncherDragManager { id: launcherDragManager @@ -48,7 +48,7 @@ DragDrop.DropArea { } anchors.fill: parent z: 999999 - appletsLayout: homeScreenContents.appletsLayout + appletsLayout: dropArea.appletsLayout favoriteStrip: dropArea.favoriteStrip } @@ -63,6 +63,7 @@ DragDrop.DropArea { event.accept(event.proposedAction); launcherDragManager.active = true; } + onDragMove: { let posInFavorites = favoriteStrip.mapFromItem(this, event.x, event.y); if (posInFavorites.y > 0) { @@ -84,13 +85,13 @@ DragDrop.DropArea { let scenePos = mapToItem(null, event.x, event.y); //SCROLL LEFT if (scenePos.x < PlasmaCore.Units.gridUnit) { - mainFlickable.scrollLeft(); + homeScreenPages.scrollLeft(); //SCROLL RIGHT - } else if (scenePos.x > mainFlickable.width - PlasmaCore.Units.gridUnit) { - mainFlickable.scrollRight(); + } else if (scenePos.x > homeScreenPages.width - PlasmaCore.Units.gridUnit) { + homeScreenPages.scrollRight(); //DON't SCROLL } else { - mainFlickable.stopScroll(); + homeScreenPages.stopScroll(); } } } @@ -159,15 +160,20 @@ DragDrop.DropArea { signal appletsLayoutInteracted TapHandler { - target: mainFlickable - enabled: appDrawer.status !== AbstractAppDrawer.Status.Open + target: homeScreenPages + enabled: homeScreenState.currentView === HomeScreenState.PageView onTapped: { //Hides icons close button appletsLayout.appletsLayoutInteracted(); appletsLayout.editMode = false; appletsLayout.forceActiveFocus(); } - onLongPressed: appletsLayout.editMode = true; + onLongPressed: { + if (homeScreenState.currentSwipeState === HomeScreenState.DeterminingType) { + // only go into edit mode when not in a swipe + appletsLayout.editMode = true; + } + } onPressedChanged: appletsLayout.focus = true; } @@ -200,20 +206,21 @@ DragDrop.DropArea { placeHolder: ContainmentLayoutManager.PlaceHolder {} //FIXME: move PlasmaComponents.Label { - id: metrics - text: "M\nM" - visible: false - font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9 - } + id: metrics + text: "M\nM" + visible: false + font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9 + } LauncherRepeater { id: launcherRepeater + homeScreenState: dropArea.homeScreenState cellWidth: appletsLayout.cellWidth cellHeight: appletsLayout.cellHeight appletsLayout: appletsLayout favoriteStrip: dropArea.favoriteStrip - onScrollLeftRequested: mainFlickable.scrollLeft() - onScrollRightRequested: mainFlickable.scrollRight() - onStopScrollRequested: mainFlickable.stopScroll() + onScrollLeftRequested: homeScreenPages.scrollLeft() + onScrollRightRequested: homeScreenPages.scrollRight() + onStopScrollRequested: homeScreenPages.stopScroll() } } } diff --git a/components/mobilehomescreencomponents/qml/FlickablePages.qml b/components/mobilehomescreencomponents/qml/HomeScreenPages.qml similarity index 61% rename from components/mobilehomescreencomponents/qml/FlickablePages.qml rename to components/mobilehomescreencomponents/qml/HomeScreenPages.qml index 84b337ea..62ef93b0 100644 --- a/components/mobilehomescreencomponents/qml/FlickablePages.qml +++ b/components/mobilehomescreencomponents/qml/HomeScreenPages.qml @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2019 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin * * SPDX-License-Identifier: LGPL-2.0-or-later */ @@ -23,23 +24,15 @@ import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenCompon Flickable { id: mainFlickable - - property AbstractAppDrawer appDrawer - - readonly property int totalPages: Math.ceil(contentWidth / width) - property int currentIndex: 0 - - property ContainmentLayoutManager.AppletsLayout appletsLayout: null + + required property var homeScreenState + property Item footer - property alias dragGestureEnabled: gestureHandler.enabled - opacity: appDrawer ? 1 - appDrawer.openFactor : 1 - transform: Translate { - y: appDrawer ? (-mainFlickable.height / 20) * appDrawer.openFactor : 0 - } - clip: true - property bool showAddPageIndicator: false + + contentX: homeScreenState.xPosition + contentHeight: height interactive: false @@ -49,9 +42,6 @@ Flickable { onFlickStarted: cancelEditModeForItemsRequested() onFlickEnded: cancelEditModeForItemsRequested() - //onCurrentIndexChanged: contentX = width * currentIndex; - onContentXChanged: mainFlickable.currentIndex = Math.floor(contentX / width) - onFooterChanged: { if (footer) { footer.parent = mainFlickable; @@ -61,7 +51,7 @@ Flickable { } } - //Autoscroll related functions + // autoscroll between pages (when holding a delegate to go to a new page) function scrollLeft() { if (mainFlickable.atXBeginning) { return; @@ -88,63 +78,16 @@ Flickable { scrollRightIndicator.opacity = 0; } - function snapPage() { - scrollAnim.running = false; - scrollAnim.to = mainFlickable.width * Math.round(mainFlickable.contentX / mainFlickable.width) - scrollAnim.running = true; - } - - function snapNextPage() { - scrollAnim.running = false; - scrollAnim.to = mainFlickable.width * Math.ceil(mainFlickable.contentX / mainFlickable.width) - scrollAnim.running = true; - } - - function snapPrevPage() { - scrollAnim.running = false; - scrollAnim.to = mainFlickable.width * Math.floor(mainFlickable.contentX / mainFlickable.width) - scrollAnim.running = true; - } - function scrollToPage(index) { - scrollAnim.running = false; - scrollAnim.to = mainFlickable.width * Math.max(0, Math.min(index, mainFlickable.contentWidth - mainFlickable.width)) - scrollAnim.running = true; - } - Timer { id: autoScrollTimer property bool scrollRight: true repeat: true interval: 1500 onTriggered: { - scrollAnim.to = scrollRight ? - //Scroll Right - Math.min(mainFlickable.contentItem.width - mainFlickable.width, mainFlickable.contentX + mainFlickable.width) : - //Scroll Left - Math.max(0, mainFlickable.contentX - mainFlickable.width); - - scrollAnim.running = true; + homeScreenState.animateGoToPageIndex(Math.max(0, homeScreenState.currentPageIndex + (scrollRight ? 1 : -1)), PlasmaCore.Units.longDuration * 2); } } - - Private.DragGestureHandler { - id: gestureHandler - target: appletsLayout - appDrawer: mainFlickable.appDrawer - mainFlickable: mainFlickable - onSnapPage: mainFlickable.snapPage(); - onSnapNextPage: mainFlickable.snapNextPage(); - onSnapPrevPage: mainFlickable.snapPrevPage(); - } - - NumberAnimation { - id: scrollAnim - target: mainFlickable - properties: "contentX" - duration: PlasmaCore.Units.longDuration - easing.type: Easing.InOutQuad - } - + PlasmaComponents.PageIndicator { id: pageIndicator anchors { @@ -152,12 +95,16 @@ Flickable { horizontalCenter: parent.horizontalCenter bottomMargin: mainFlickable.footer ? mainFlickable.footer.height : 0 } + PlasmaCore.ColorScope.inherit: false PlasmaCore.ColorScope.colorGroup: PlasmaCore.Theme.ComplementaryColorGroup + parent: mainFlickable - count: mainFlickable.totalPages visible: count > 1 - currentIndex: Math.round(mainFlickable.contentX / mainFlickable.width) + + count: homeScreenState.pagesCount + currentIndex: homeScreenState.currentPageIndex + delegate: Rectangle { property bool isAddPageIndicator: index === pageIndicator.count-1 && mainFlickable.showAddPageIndicator implicitWidth: PlasmaCore.Units.gridUnit/2 @@ -166,14 +113,13 @@ Flickable { radius: width color: isAddPageIndicator ? "transparent" : PlasmaCore.ColorScope.textColor - PlasmaComponents.Label { anchors.centerIn: parent visible: parent.isAddPageIndicator text: "⊕" } - opacity: index === currentIndex ? 0.9 : pressed ? 0.7 : 0.5 + opacity: index === pageIndicator.currentIndex ? 0.9 : pressed ? 0.7 : 0.5 Behavior on opacity { OpacityAnimator { duration: PlasmaCore.Units.longDuration diff --git a/components/mobilehomescreencomponents/qml/HomeScreenState.qml b/components/mobilehomescreencomponents/qml/HomeScreenState.qml new file mode 100644 index 00000000..eaebde93 --- /dev/null +++ b/components/mobilehomescreencomponents/qml/HomeScreenState.qml @@ -0,0 +1,413 @@ +/* + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 + +import org.kde.plasma.core 2.1 as PlasmaCore + +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +/** + * State object for the homescreen. + */ +QtObject { + id: root + + required property real totalPagesWidth + + required property var appDrawerFlickable + + // dimensions of the homescreen area (not including top panel and task panel) + required property real availableScreenHeight + required property real availableScreenWidth + + // offset from the bottom of the screen that the app drawer starts from, + // would be the height favourites strip + required property real appDrawerBottomOffset + + // ~~ positioning ~~ + + // xPosition: + // We start at 0, which is the beginning x position of the row of pages (left-most side). + // Increasing x moves *right* toward the next page. + // + // yPosition: + // Increasing y results in moving *up* in the view. + // appDrawerOpenYPosition - The app drawer is opened (app drawer flickable is active iff it's not at the beginning). + // pagesYPosition - The app drawer is closed. Homescreen pages are visible, can swipe left/right between pages. + property real xPosition: 0 + property real yPosition: pagesYPosition + + // direction of the movement + property bool movingRight: false + property bool movingUp: false + + // used for calculating movement direction + property real oldXPosition: 0 + property real oldYPosition: 0 + onXPositionChanged: { + movingRight = xPosition > oldXPosition; + oldXPosition = xPosition; + } + onYPositionChanged: { + movingUp = yPosition > oldYPosition; + oldYPosition = yPosition; + } + + // yPosition when the homescreen pages are visible + readonly property real pagesYPosition: availableScreenHeight - appDrawerBottomOffset + + // yPosition when drawer is open + readonly property real appDrawerOpenYPosition: 0 + + // ~~ active state ~~ + + enum View { + PageView, // we are viewing the horizontal row of pages + AppDrawerBeginningView, // we are at the top of the app drawer (could either close it or scroll down) + AppDrawerView // we are in the app drawer, and not at the top of it + } + + // the current view of the homescreen + property var currentView: HomeScreenState.PageView + + // number of homescreen pages + readonly property int pagesCount: Math.floor(totalPagesWidth / pageWidth) + + // current homescreen page index + readonly property int currentPageIndex: { + let candidateIndex = Math.round(xPosition / (pageSpacing + pageWidth)); + return Math.max(0, Math.min(pagesCount - 1, candidateIndex)); + } + + enum PageViewSwipeState { + SwipingPages, // horizontal movement between pages + SwipingAppDrawerVisibility, // opening/closing app drawer + SwipingAppDrawerList, // scrolling app drawer + SwipingActionPanel, // pulling down action panel + DeterminingType + } + + // when we are at the PageView view, we need to distinguish horizontal swipes (changing pages) + // and vertical swipes (opening drawer) + property var currentSwipeState: HomeScreenState.DeterminingType + + // threshold of movement in a direction before we count that as the defining SwipeState + readonly property real swipeStateDetermineThreshold: PlasmaCore.Units.smallSpacing + + // we put the offset position here when determining the swipe type, before we + // transfer movement over to xPosition and yPosition + property real xDetermineSwipePosition: 0 + property real yDetermineSwipePosition: 0 + + // whether animations are currently running + property bool animationsRunning: openDrawerAnim.running || closeDrawerAnim.running || xAnim.running + + // whether the app drawer flickable should be interactive + property bool appDrawerInteractive: currentView === HomeScreenState.AppDrawerView + + // ~~ measurement constants ~~ + + // dimensions of a page + readonly property real pageHeight: availableScreenHeight + readonly property real pageWidth: availableScreenWidth + + // spacing between each homescreen page + readonly property real pageSpacing: 0 + + // ~~ signals and functions ~~ + + // cancel edit mode + signal cancelEditModeForItemsRequested + + // cancel all animated moving, as another flick source is taking over + signal cancelAnimations() + onCancelAnimations: { + openDrawerAnim.stop(); + closeDrawerAnim.stop(); + xAnim.stop(); + } + + // be very careful when resetting the swipe state + // ensure that we aren't in the middle of a gesture + function resetSwipeState() { + currentSwipeState = HomeScreenState.DeterminingType; + xDetermineSwipePosition = 0; + yDetermineSwipePosition = 0; + } + + function openAppDrawer() { + openDrawerAnim.restart(); + } + + function openAppDrawerInstantly() { + yPosition = appDrawerOpenYPosition; + currentView = HomeScreenState.AppDrawerBeginningView; + } + + function closeAppDrawer() { + closeDrawerAnim.restart(); + } + + function closeAppDrawerInstantly() { + yPosition = pagesYPosition; + currentView = HomeScreenState.PageView; + } + + // get the xPosition where the page will be centered on the screen + function xPositionFromPageIndex(index) { + return index * (pageWidth + pageSpacing); + } + + // instantly go to the page index + function goToPageIndex(index) { + xPosition = xPositionFromPageIndex(index); + } + + // go to the page index, animated + function animateGoToPageIndex(index, duration) { + xAnim.duration = duration; + xAnim.to = xPositionFromPageIndex(index); + xAnim.restart(); + } + + // update the position using an offset + // called by swipe provider flickable + function updatePositionWithOffset(x, y) { + switch (currentView) { + case HomeScreenState.PageView: { + switch (currentSwipeState) { + case HomeScreenState.DeterminingType: + xDetermineSwipePosition += x; + yDetermineSwipePosition += y; + + // check if a swipetype can be determined and started + if (Math.abs(xDetermineSwipePosition) >= swipeStateDetermineThreshold) { + currentSwipeState = HomeScreenState.SwipingPages; + xDetermineSwipePosition = 0; + yDetermineSwipePosition = 0; + } else if (yDetermineSwipePosition >= swipeStateDetermineThreshold) { + currentSwipeState = HomeScreenState.SwipingActionPanel; + MobileShell.TopPanelControls.startSwipe(); + xDetermineSwipePosition = 0; + yDetermineSwipePosition = 0; + } else if (-yDetermineSwipePosition >= swipeStateDetermineThreshold) { + currentSwipeState = HomeScreenState.SwipingAppDrawerVisibility; + xDetermineSwipePosition = 0; + yDetermineSwipePosition = 0; + } + break; + + case HomeScreenState.SwipingPages: + xPosition += x; + break; + + case HomeScreenState.SwipingActionPanel: + yPosition = pagesYPosition; + if (y !== 0) { + MobileShell.TopPanelControls.requestRelativeScroll(y); + } + break; + + case HomeScreenState.SwipingAppDrawerVisibility: + yPosition = Math.max(appDrawerOpenYPosition, Math.min(pagesYPosition, yPosition + y)); + break; + } + break; + } + + case HomeScreenState.AppDrawerBeginningView: { + switch (currentSwipeState) { + case HomeScreenState.DeterminingType: + xDetermineSwipePosition += x; + yDetermineSwipePosition += y; + + // check if a swipetype can be determined and started + if (yDetermineSwipePosition >= swipeStateDetermineThreshold) { + currentSwipeState = HomeScreenState.SwipingAppDrawerVisibility; + xDetermineSwipePosition = 0; + yDetermineSwipePosition = 0; + } else if (-yDetermineSwipePosition >= swipeStateDetermineThreshold) { + currentSwipeState = HomeScreenState.SwipingAppDrawerList; + yVelocityCalculator.startMeasure(appDrawerFlickable.contentY); + xDetermineSwipePosition = 0; + yDetermineSwipePosition = 0; + } + break; + case HomeScreenState.SwipingAppDrawerVisibility: + yPosition = Math.max(appDrawerOpenYPosition, Math.min(pagesYPosition, yPosition + y)); + break; + + case HomeScreenState.SwipingAppDrawerList: + // app drawer scrolling + let candidateNewPos = appDrawerFlickable.contentY - y; + appDrawerFlickable.contentY = candidateNewPos; + // update velocity + yVelocityCalculator.changePosition(appDrawerFlickable.contentY); + break; + } + break; + } + case HomeScreenState.AppDrawerView: { + break; + } + } + } + + // called after a user finishes an interaction (ex. lets go of the screen) + // called by swipe provider flickable + function updateState() { + cancelAnimations(); + + // we need to always call resetSwipeState() after each interaction. + // if we have an animation to run, we rely on the animation to call the function. + // otherwise, we do it directly here. + + switch (currentView) { + case HomeScreenState.PageView: { + + // update vertical position + switch (currentSwipeState) { + case HomeScreenState.DeterminingType: { + movingUp ? closeAppDrawer() : openAppDrawer(); + break; + } + + case HomeScreenState.SwipingActionPanel: { + MobileShell.TopPanelControls.endSwipe(); + root.resetSwipeState(); + break; + } + + case HomeScreenState.SwipingAppDrawerVisibility: { + movingUp ? closeAppDrawer() : openAppDrawer(); + break; + } + + case HomeScreenState.SwipingPages: { + // update pages position + let currentPageIndexPosition = xPositionFromPageIndex(currentPageIndex); + let duration = PlasmaCore.Units.longDuration * 2; + + if (xPosition < currentPageIndexPosition) { + if (movingRight) { + animateGoToPageIndex(currentPageIndex, duration); + } else { + animateGoToPageIndex(Math.max(0, currentPageIndex - 1), duration); + } + } else { + if (movingRight) { + animateGoToPageIndex(Math.min(pagesCount - 1, currentPageIndex + 1), duration); + } else { + animateGoToPageIndex(currentPageIndex, duration); + } + } + break; + } + + default: { + // this shouldn't occur, but keeps consistent state if it does + root.resetSwipeState(); + break; + } + } + + break; + } + case HomeScreenState.AppDrawerBeginningView: { + + switch (currentSwipeState) { + case HomeScreenState.DeterminingType: + case HomeScreenState.SwipingAppDrawerVisibility: { + movingUp ? closeAppDrawer() : openAppDrawer(); + break; + } + case HomeScreenState.SwipingAppDrawerList: { + currentView = HomeScreenState.AppDrawerView; + appDrawerFlickable.flick(0, -yVelocityCalculator.velocity); + root.resetSwipeState(); + break; + } + default: { + // this shouldn't occur, but keeps consistent state if it does + root.resetSwipeState(); + break; + } + } + + break; + } + case HomeScreenState.AppDrawerView: { + break; + } + } + } + + // measure velocity of our swipe in the app drawer, so that we can flick + property var yVelocityCalculator: MobileShell.VelocityCalculator {} + + // listen to the app drawer's flickable for if it goes to the top of the list + // we then update our view state + property var appDrawerFlickableListener: Connections { + target: appDrawerFlickable + + function onMovementEnded() { + if (root.currentView === HomeScreenState.AppDrawerView) { + if (appDrawerFlickable.contentY <= 0) { + root.currentView = HomeScreenState.AppDrawerBeginningView; + } + } + } + + function onDraggingChanged() { + if (!appDrawerFlickable.dragging) { + if (root.currentView === HomeScreenState.AppDrawerView) { + if (appDrawerFlickable.contentY <= 0) { + root.currentView = HomeScreenState.AppDrawerBeginningView; + } + } + } + } + } + + // ~~ property animators ~~ + + property var xAnim: NumberAnimation { + target: root + property: "xPosition" + easing.type: Easing.OutBack + onFinished: { + root.resetSwipeState(); + } + } + + property var openDrawerAnim: NumberAnimation { + target: root + property: "yPosition" + to: appDrawerOpenYPosition + duration: PlasmaCore.Units.longDuration * 2 + easing.type: Easing.OutCubic + + onFinished: { + root.currentView = HomeScreenState.AppDrawerBeginningView; + root.resetSwipeState(); + } + } + + property var closeDrawerAnim: NumberAnimation { + target: root + property: "yPosition" + to: pagesYPosition + duration: PlasmaCore.Units.longDuration * 2 + easing.type: Easing.OutCubic + + onFinished: { + root.currentView = HomeScreenState.PageView; + root.resetSwipeState(); + } + } +} + diff --git a/components/mobilehomescreencomponents/qml/LauncherGrid.qml b/components/mobilehomescreencomponents/qml/LauncherGrid.qml deleted file mode 100644 index 379a124b..00000000 --- a/components/mobilehomescreencomponents/qml/LauncherGrid.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2019 Marco Martin - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -import QtQuick 2.4 -import QtQuick.Layouts 1.1 -import QtQuick.Controls 2.3 as Controls - -import org.kde.plasma.plasmoid 2.0 -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.kquickcontrolsaddons 2.0 -import org.kde.kirigami 2.10 as Kirigami -import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager - -import org.kde.plasma.private.nanoshell 2.0 as NanoShell -import org.kde.plasma.private.mobileshell 1.0 as MobileShell - -import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents - -LauncherContainer { - id: root - - readonly property int columns: Math.floor(root.flow.width / cellWidth) - readonly property int cellWidth: root.flow.width / Math.floor(root.flow.width / ((availableCellHeight - reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4)) - readonly property int cellHeight: availableCellHeight - - signal launched - - frame.width: width - - Repeater { - parent: root.flow - model: HomeScreenComponents.ApplicationListModel - delegate: HomeDelegate { - id: delegate - width: root.cellWidth - height: root.cellHeight - - parent: parentFromLocation - property Item parentFromLocation: { - switch (model.applicationLocation) { - case HomeScreenComponents.ApplicationListModel.Desktop: - return appletsLayout; - case HomeScreenComponents.ApplicationListModel.Favorites: - return favoriteStrip.flow; - default: - return root.flow; - } - } - Component.onCompleted: { - if (model.applicationLocation === HomeScreenComponents.ApplicationListModel.Desktop) { - appletsLayout.restoreItem(delegate); - } - } - onLaunch: (x, y, icon, title) => { - if (icon !== "") { - MobileShell.HomeScreenControls.openAppAnimation( - icon, - title, - delegate.iconItem.Kirigami.ScenePosition.x + delegate.iconItem.width/2, - delegate.iconItem.Kirigami.ScenePosition.y + delegate.iconItem.height/2, - Math.min(delegate.iconItem.width, delegate.iconItem.height)); - } - root.launched(); - } - onParentFromLocationChanged: { - if (!launcherDragManager.active && parent != parentFromLocation) { - parent = parentFromLocation; - if (model.applicationLocation === HomeScreenComponents.ApplicationListModel.Favorites) { - plasmoid.nativeInterface.stackBefore(delegate, parentFromLocation.children[index]); - - } else if (model.applicationLocation === HomeScreenComponents.ApplicationListModel.Grid) { - plasmoid.nativeInterface.stackBefore(delegate, parentFromLocation.children[Math.max(0, index - HomeScreenComponents.ApplicationListModel.favoriteCount)]); - } - } - } - } - } -} - diff --git a/components/mobilehomescreencomponents/qml/LauncherRepeater.qml b/components/mobilehomescreencomponents/qml/LauncherRepeater.qml index d0eea792..23fa8102 100644 --- a/components/mobilehomescreencomponents/qml/LauncherRepeater.qml +++ b/components/mobilehomescreencomponents/qml/LauncherRepeater.qml @@ -22,6 +22,9 @@ import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenCompon Repeater { id: launcherRepeater model: HomeScreenComponents.FavoritesModel + + required property var homeScreenState + property ContainmentLayoutManager.AppletsLayout appletsLayout property FavoriteStrip favoriteStrip property int cellWidth @@ -33,6 +36,8 @@ Repeater { delegate: HomeDelegate { id: delegate + homeScreenState: launcherRepeater.homeScreenState + width: launcherRepeater.cellWidth height: Math.min(parent.height, launcherRepeater.cellHeight) appletsLayout: launcherRepeater.appletsLayout @@ -74,7 +79,7 @@ Repeater { if (pos.x < PlasmaCore.Units.gridUnit) { launcherRepeater.scrollLeftRequested(); //SCROLL RIGHT - } else if (pos.x > mainFlickable.width - PlasmaCore.Units.gridUnit) { + } else if (pos.x > homeScreenState.pageWidth - PlasmaCore.Units.gridUnit) { launcherRepeater.scrollRightRequested(); //DON't SCROLL } else { diff --git a/components/mobilehomescreencomponents/qml/AbstractAppDrawer.qml b/components/mobilehomescreencomponents/qml/appdrawer/AbstractAppDrawer.qml similarity index 61% rename from components/mobilehomescreencomponents/qml/AbstractAppDrawer.qml rename to components/mobilehomescreencomponents/qml/appdrawer/AbstractAppDrawer.qml index 07ecbb51..2bcb1229 100644 --- a/components/mobilehomescreencomponents/qml/AbstractAppDrawer.qml +++ b/components/mobilehomescreencomponents/qml/appdrawer/AbstractAppDrawer.qml @@ -20,44 +20,21 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents -import "private" +import "../private" +import "../" Item { id: root - - enum Status { - Closed, - Peeking, - Open - } - - enum MovementDirection { - None = 0, - Up, - Down - } - - readonly property int status: { - if (flickable.contentY >= topMargin.height) { - return AbstractAppDrawer.Status.Open; - } else if (flickable.contentY > 0) { - return AbstractAppDrawer.Status.Peeking; - } else { - return AbstractAppDrawer.Status.Closed; - } - } - - property real offset: 0 - property real closedPositionOffset: 0 + required property var homeScreenState property real leftPadding: 0 property real topPadding: 0 property real bottomPadding: 100 property real rightPadding: 0 - property alias flickable: view + property alias flickable: flickableBody.contentItem - property var contentItem + property Flickable contentItem property real contentWidth: holdingColumn.width required property int headerHeight @@ -69,54 +46,14 @@ Item { readonly property int reservedSpaceForLabel: metrics.height property int availableCellHeight: PlasmaCore.Units.iconSizes.huge + reservedSpaceForLabel - readonly property real openFactor: factorNormalize(flickable.contentY / (units.gridUnit * 10)) + readonly property real openFactor: factorNormalize(view.contentY / (PlasmaCore.Units.gridUnit * 10)) // height from top of screen that the drawer starts readonly property real drawerTopMargin: height - topPadding - bottomPadding - closedPositionOffset + readonly property real closedPositionOffset: homeScreenState.appDrawerBottomOffset //BEGIN functions - function goToBeginning() { - scrollAnim.to = drawerTopMargin; - scrollAnim.restart(); - } - - function open() { - if (root.status === AbstractAppDrawer.Status.Open) { - flickable.flick(0,1); - } else { - goToBeginning(); - } - } - - function close() { - if (root.status !== AbstractAppDrawer.Status.Closed) { - scrollAnim.to = 0; - scrollAnim.restart(); - } - } - - // snap the drawer to an open or close position - function snapDrawerStatus() { - if (flickable.contentY > topMargin.height) { - return; - } - - if (flickable.movementDirection === AbstractAppDrawer.MovementDirection.Up) { - if (flickable.contentY > topMargin.height / 8) { // over one eighth of the screen - open(); - } else { - close(); - } - } else { - if (flickable.contentY < 7 * topMargin.height / 8) { // over one eighth of the screen - close(); - } else { - open(); - } - } - } - function factorNormalize(num) { return Math.min(1, Math.max(0, num)); } @@ -125,15 +62,6 @@ Item { Drag.dragType: Drag.Automatic - NumberAnimation { - id: scrollAnim - target: flickable - properties: "contentY" - duration: PlasmaCore.Units.longDuration * 2 - easing.type: Easing.OutQuad - easing.amplitude: 2.0 - } - PC3.Label { id: metrics text: "M\nM" @@ -143,7 +71,7 @@ Item { // bottom divider GradientBar { - opacity: root.status !== AbstractAppDrawer.Status.Closed ? 0.6 : 0 + opacity: (homeScreenState.currentView !== HomeScreenState.PageView || homeScreenState.currentSwipeState === HomeScreenState.SwipingAppDrawerVisibility) ? 0.6 : 0 visible: root.bottomPadding > 0 anchors.left: parent.left anchors.right: parent.right @@ -156,25 +84,14 @@ Item { id: view anchors.fill: parent - // We have a situation where this vertical flickable conflicts with the horizontal flickable used for homescreen pages. - // This flickable is on top of the other, so we disable it when it isn't open. - // We do the initial open gesture in private/DragGestureHandler.qml - interactive: contentY > PlasmaCore.Units.gridUnit + // scroll events are handled by our flick container, we are only using this for positioning + interactive: false + contentY: Math.max(0, Math.min(root.drawerTopMargin, root.drawerTopMargin - homeScreenState.yPosition)) contentHeight: column.implicitHeight contentWidth: -1 boundsBehavior: Flickable.StopAtBounds - // snap - onMovementEnded: root.snapDrawerStatus() - - property int movementDirection: AbstractAppDrawer.MovementDirection.None - property real oldContentY - onContentYChanged: { // update state - movementDirection = oldContentY > contentY ? AbstractAppDrawer.MovementDirection.Down : AbstractAppDrawer.MovementDirection.Up; - oldContentY = contentY; - } - ColumnLayout { id: column width: view.width @@ -196,8 +113,8 @@ Item { } factor: root.openFactor flickable: view - onOpenRequested: root.open(); - onCloseRequested: root.close(); + onOpenRequested: homeScreenState.openAppDrawer(); + onCloseRequested: homeScreenState.closeAppDrawer(); } } @@ -206,8 +123,6 @@ Item { id: drawerFlickable Layout.fillWidth: true Layout.preferredHeight: root.height - - visible: view.interactive // this is so that the favourites strip can be interacted with leftPadding: root.leftPadding; topPadding: root.topPadding rightPadding: root.rightPadding; bottomPadding: root.bottomPadding diff --git a/containments/homescreen/package/contents/ui/AppDrawerHeader.qml b/components/mobilehomescreencomponents/qml/appdrawer/AppDrawerHeader.qml similarity index 100% rename from containments/homescreen/package/contents/ui/AppDrawerHeader.qml rename to components/mobilehomescreencomponents/qml/appdrawer/AppDrawerHeader.qml diff --git a/components/mobilehomescreencomponents/qml/appdrawer/AppDrawerLoader.qml b/components/mobilehomescreencomponents/qml/appdrawer/AppDrawerLoader.qml new file mode 100644 index 00000000..c1a1a383 --- /dev/null +++ b/components/mobilehomescreencomponents/qml/appdrawer/AppDrawerLoader.qml @@ -0,0 +1,82 @@ +/* + * SPDX-FileCopyrightText: 2019 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PlasmaComponents + +import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents +import org.kde.plasma.private.mobileshell 1.0 as MobileShell + +Loader { + id: root + + required property var homeScreenState + + property real topPadding: 0 + property real bottomPadding: 0 + property real leftPadding: 0 + property real rightPadding: 0 + + property string appDrawerType: "gridview" // gridview/listview + + readonly property real headerHeight: Math.round(PlasmaCore.Units.gridUnit * 3) + + sourceComponent: appDrawerType === "gridview" ? gridViewDrawer : listViewDrawer + + Component { + id: headerComponent + + AppDrawerHeader { + onSwitchToListRequested: { + if (root.appDrawerType !== "listview") { + root.appDrawerType = "listview"; + } + } + + onSwitchToGridRequested: { + if (root.appDrawerType !== "gridview") { + root.appDrawerType = "gridview"; + } + } + } + } + + Component { + id: listViewDrawer + ListViewAppDrawer { + anchors.fill: parent + topPadding: root.topPadding + bottomPadding: root.bottomPadding + leftPadding: root.leftPadding + rightPadding: root.rightPadding + + homeScreenState: root.homeScreenState + headerItem: Loader { sourceComponent: headerComponent } + headerHeight: root.headerHeight + } + } + + Component { + id: gridViewDrawer + GridViewAppDrawer { + anchors.fill: parent + topPadding: root.topPadding + bottomPadding: root.bottomPadding + leftPadding: root.leftPadding + rightPadding: root.rightPadding + + homeScreenState: root.homeScreenState + headerItem: Loader { sourceComponent: headerComponent } + headerHeight: root.headerHeight + } + } +} diff --git a/components/mobilehomescreencomponents/qml/DrawerGridDelegate.qml b/components/mobilehomescreencomponents/qml/appdrawer/DrawerGridDelegate.qml similarity index 100% rename from components/mobilehomescreencomponents/qml/DrawerGridDelegate.qml rename to components/mobilehomescreencomponents/qml/appdrawer/DrawerGridDelegate.qml diff --git a/components/mobilehomescreencomponents/qml/DrawerListDelegate.qml b/components/mobilehomescreencomponents/qml/appdrawer/DrawerListDelegate.qml similarity index 100% rename from components/mobilehomescreencomponents/qml/DrawerListDelegate.qml rename to components/mobilehomescreencomponents/qml/appdrawer/DrawerListDelegate.qml diff --git a/components/mobilehomescreencomponents/qml/GridViewAppDrawer.qml b/components/mobilehomescreencomponents/qml/appdrawer/GridViewAppDrawer.qml similarity index 76% rename from components/mobilehomescreencomponents/qml/GridViewAppDrawer.qml rename to components/mobilehomescreencomponents/qml/appdrawer/GridViewAppDrawer.qml index 808b739f..9a32a6ba 100644 --- a/components/mobilehomescreencomponents/qml/GridViewAppDrawer.qml +++ b/components/mobilehomescreencomponents/qml/appdrawer/GridViewAppDrawer.qml @@ -20,7 +20,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents -import "private" +import "../private" AbstractAppDrawer { id: root @@ -28,32 +28,7 @@ AbstractAppDrawer { contentItem: GridView { id: gridView clip: true - - // start location of dragging - property real startDragContentY - onMovementStarted: { - oldContentY = contentY; - startDragContentY = contentY; - } - - // move drawer down when at the top of the app list - property real oldContentY - property bool movingDrawerDown: false - onContentYChanged: { - let candidateContentY = root.flickable.contentY - (oldContentY - contentY); - if (dragging && startDragContentY <= 0 && oldContentY <= 0 && candidateContentY <= root.drawerTopMargin) { - root.flickable.contentY = candidateContentY; - contentY = 0; - movingDrawerDown = true; - } - oldContentY = contentY; - } - onMovementEnded: { - if (movingDrawerDown) { - root.snapDrawerStatus(); - movingDrawerDown = false; - } - } + interactive: root.homeScreenState.appDrawerInteractive cellWidth: root.contentWidth / Math.floor(root.contentWidth / ((root.availableCellHeight - root.reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4)) cellHeight: root.availableCellHeight @@ -78,7 +53,7 @@ AbstractAppDrawer { root.Drag.hotSpot.y = y; root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData }; - root.close() + root.homeScreenState.closeAppDrawer() root.dragStarted() root.Drag.active = true; diff --git a/components/mobilehomescreencomponents/qml/ListViewAppDrawer.qml b/components/mobilehomescreencomponents/qml/appdrawer/ListViewAppDrawer.qml similarity index 75% rename from components/mobilehomescreencomponents/qml/ListViewAppDrawer.qml rename to components/mobilehomescreencomponents/qml/appdrawer/ListViewAppDrawer.qml index 3ecc59ae..ccd2af74 100644 --- a/components/mobilehomescreencomponents/qml/ListViewAppDrawer.qml +++ b/components/mobilehomescreencomponents/qml/appdrawer/ListViewAppDrawer.qml @@ -20,7 +20,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents -import "private" +import "../private" AbstractAppDrawer { id: root @@ -31,31 +31,7 @@ AbstractAppDrawer { reuseItems: true cacheBuffer: model.count * delegateHeight // delegate height - // start location of dragging - property real startDragContentY - onMovementStarted: { - oldContentY = contentY; - startDragContentY = contentY; - } - - // move drawer down when at the top of the app list - property real oldContentY - property bool movingDrawerDown: false - onContentYChanged: { - let candidateContentY = root.flickable.contentY - (oldContentY - contentY); - if (dragging && startDragContentY <= 0 && oldContentY <= 0 && candidateContentY <= root.drawerTopMargin) { - root.flickable.contentY = candidateContentY; - contentY = 0; - movingDrawerDown = true; - } - oldContentY = contentY; - } - onMovementEnded: { - if (movingDrawerDown) { - root.snapDrawerStatus(); - movingDrawerDown = false; - } - } + interactive: root.homeScreenState.appDrawerInteractive property int delegateHeight: PlasmaCore.Units.gridUnit * 3 @@ -74,7 +50,7 @@ AbstractAppDrawer { root.Drag.hotSpot.y = y; root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData }; - root.close() + root.homeScreenState.closeAppDrawer() root.dragStarted() root.Drag.active = true; diff --git a/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml b/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml index 4e8d497a..c888eb5c 100644 --- a/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml +++ b/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml @@ -22,6 +22,7 @@ import org.kde.plasma.private.mobileshell 1.0 as MobileShell MouseArea { id: arrowUpIcon z: 9 + property Flickable flickable property real factor: 0 @@ -32,7 +33,6 @@ MouseArea { onClicked: { openRequested(); - scrollAnim.restart(); } Item { diff --git a/components/mobilehomescreencomponents/qml/qmldir b/components/mobilehomescreencomponents/qml/qmldir index bb74f965..10a5cc65 100644 --- a/components/mobilehomescreencomponents/qml/qmldir +++ b/components/mobilehomescreencomponents/qml/qmldir @@ -6,18 +6,23 @@ module org.kde.plasma.private.mobilehomescreencomponents plugin mobilehomescreencomponentsplugin -AbstractAppDrawer 0.1 AbstractAppDrawer.qml -GridViewAppDrawer 0.1 GridViewAppDrawer.qml -ListViewAppDrawer 0.1 ListViewAppDrawer.qml -DrawerListDelegate 0.1 DrawerListDelegate.qml -DrawerGridDelegate 0.1 DrawerGridDelegate.qml +AbstractAppDrawer 0.1 appdrawer/AbstractAppDrawer.qml +AppDrawerHeader 0.1 appdrawer/AppDrawerHeader.qml +AppDrawerLoader 0.1 appdrawer/AppDrawerLoader.qml +DrawerGridDelegate 0.1 appdrawer/DrawerGridDelegate.qml +DrawerListDelegate 0.1 appdrawer/DrawerListDelegate.qml +GridViewAppDrawer 0.1 appdrawer/GridViewAppDrawer.qml +ListViewAppDrawer 0.1 appdrawer/ListViewAppDrawer.qml + FavoriteStrip 0.1 FavoriteStrip.qml -FlickablePages 0.1 FlickablePages.qml +FlickContainer 0.1 FlickContainer.qml HomeDelegate 0.1 HomeDelegate.qml +HomeScreen 0.1 HomeScreen.qml HomeScreenContents 0.1 HomeScreenContents.qml +HomeScreenPages 0.1 HomeScreenPages.qml +HomeScreenState 0.1 HomeScreenState.qml LauncherContainer 0.1 LauncherContainer.qml LauncherDragManager 0.1 LauncherDragManager.qml LauncherGrid 0.1 LauncherGrid.qml LauncherRepeater 0.1 LauncherRepeater.qml MobileAppletContainer 0.1 MobileAppletContainer.qml - diff --git a/components/mobileshell/qml/components/VelocityCalculator.qml b/components/mobileshell/qml/components/VelocityCalculator.qml new file mode 100644 index 00000000..fb8cf052 --- /dev/null +++ b/components/mobileshell/qml/components/VelocityCalculator.qml @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2013 Canonical Ltd. + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import QtQuick 2.15 + +QtObject { + id: root + + property bool zeroVelocityCounts: false + + property real velocity: 0 + + function changePosition(position) { + __pushDragEvent(position); + } + + function startMeasure(position) { + __dragEvents = []; + __pushDragEvent(position); + } + +//BEGIN internal + + property var __dragEvents: [] + property var __dateTime: new function() { + this.getCurrentTimeMs = function() {return new Date().getTime()} + } + + function __updateSpeed() { + var totalSpeed = 0; + for (var i = 0; i < __dragEvents.length; i++) { + totalSpeed += __dragEvents[i][2]; + } + + if (zeroVelocityCounts || Math.abs(totalSpeed) > 0.001) { + velocity = totalSpeed / __dragEvents.length * 1000; + } + } + + function __cullOldDragEvents(currentTime) { + // cull events older than 50 ms but always keep the latest 2 events + for (var numberOfCulledEvents = 0; numberOfCulledEvents < __dragEvents.length-2; numberOfCulledEvents++) { + // __dragEvents[numberOfCulledEvents][0] is the dragTime + if (currentTime - __dragEvents[numberOfCulledEvents][0] <= 50) break; + } + + __dragEvents.splice(0, numberOfCulledEvents); + } + + function __getEventSpeed(currentTime, position) { + if (__dragEvents.length != 0) { + var lastDrag = __dragEvents[__dragEvents.length-1]; + var duration = Math.max(1, currentTime - lastDrag[0]); + return (position - lastDrag[1]) / duration; + } else { + return 0; + } + } + + function __pushDragEvent(position) { + let currentTime = __dateTime.getCurrentTimeMs(); + __dragEvents.push([currentTime, position, __getEventSpeed(currentTime, position)]); + __cullOldDragEvents(currentTime); + __updateSpeed(); + } + +//END internal +} diff --git a/components/mobileshell/qml/qmldir b/components/mobileshell/qml/qmldir index 567dabad..ac86946f 100644 --- a/components/mobileshell/qml/qmldir +++ b/components/mobileshell/qml/qmldir @@ -13,6 +13,7 @@ ActionDrawerOpenSurface 1.0 actiondrawer/ActionDrawerOpenSurface.qml BaseItem 1.0 components/BaseItem.qml Direction 1.0 components/Direction.qml StartupFeedback 1.0 components/StartupFeedback.qml +VelocityCalculator 1.0 components/VelocityCalculator.qml # /dataproviders singleton BatteryProvider 1.0 dataproviders/BatteryProvider.qml diff --git a/containments/homescreen/package/contents/ui/HomeScreen.qml b/containments/homescreen/package/contents/ui/HomeScreen.qml deleted file mode 100644 index 0ff33509..00000000 --- a/containments/homescreen/package/contents/ui/HomeScreen.qml +++ /dev/null @@ -1,153 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2019 Marco Martin - * SPDX-FileCopyrightText: 2021 Devin Lin - * - * SPDX-License-Identifier: LGPL-2.0-or-later - */ - -import QtQuick 2.12 -import QtQuick.Window 2.12 -import QtQuick.Layouts 1.1 - -import org.kde.plasma.plasmoid 2.0 -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 3.0 as PlasmaComponents - -import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents -import org.kde.plasma.private.mobileshell 1.0 as MobileShell - -Item { - id: root - - property alias flickablePages: mainFlickable - property alias homeScreenContents: contents - - // listview/gridview header - property string appDrawerType: "gridview" // gridview/listview - property alias appDrawer: appDrawerLoader.item - - readonly property real headerHeight: Math.round(PlasmaCore.Units.gridUnit * 3) - -//BEGIN functions - - function activate() { - // there's a couple of steps: - // - minimize windows - // - open app drawer - // - restore windows - if (!plasmoid.nativeInterface.showingDesktop) { - plasmoid.nativeInterface.showingDesktop = true - } else if (appDrawer.status !== HomeScreenComponents.AbstractAppDrawer.Status.Open) { - mainFlickable.currentIndex = 0 - root.appDrawer.open() - } else { - plasmoid.nativeInterface.showingDesktop = false - root.appDrawer.close() - } - } - -//END functions - - HomeScreenComponents.FlickablePages { - id: mainFlickable - - anchors { - fill: parent - topMargin: plasmoid.availableScreenRect.y - bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y - } - - appletsLayout: homeScreenContents.appletsLayout - - appDrawer: root.appDrawer - contentWidth: Math.max(width, width * Math.ceil(homeScreenContents.itemsBoundingRect.width/width)) + (homeScreenContents.launcherDragManager.active ? width : 0) - showAddPageIndicator: homeScreenContents.launcherDragManager.active - - dragGestureEnabled: root.parent.focus && (!root.appDrawer || root.appDrawer.status !== HomeScreenComponents.AbstractAppDrawer.Status.Open) && !appletsLayout.editMode && !plasmoid.editMode && !homeScreenContents.launcherDragManager.active - - HomeScreenComponents.HomeScreenContents { - id: contents - width: mainFlickable.width * 100 - favoriteStrip: favoriteStrip - } - - footer: HomeScreenComponents.FavoriteStrip { - id: favoriteStrip - - appletsLayout: homeScreenContents.appletsLayout - visible: favoriteStrip.flow.children.length > 0 || homeScreenContents.launcherDragManager.active || homeScreenContents.containsDrag - opacity: homeScreenContents.launcherDragManager.active && HomeScreenComponents.ApplicationListModel.favoriteCount >= HomeScreenComponents.ApplicationListModel.maxFavoriteCount ? 0.3 : 1 - - TapHandler { - target: favoriteStrip - onTapped: { - //Hides icons close button - homeScreenContents.appletsLayout.appletsLayoutInteracted(); - homeScreenContents.appletsLayout.editMode = false; - } - onLongPressed: homeScreenContents.appletsLayout.editMode = true; - onPressedChanged: root.parent.focus = true; - } - } - } - - Component { - id: headerComponent - - AppDrawerHeader { - onSwitchToListRequested: { - if (root.appDrawerType !== "listview") { - root.appDrawerType = "listview"; - appDrawer.flickable.goToBeginning(); // jump to top - } - } - - onSwitchToGridRequested: { - if (root.appDrawerType !== "gridview") { - root.appDrawerType = "gridview"; - appDrawer.flickable.goToBeginning(); // jump to top - } - } - } - } - - Component { - id: listViewDrawer - HomeScreenComponents.ListViewAppDrawer { - anchors.fill: parent - topPadding: plasmoid.availableScreenRect.y - - // pad for navbar - rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth - bottomPadding: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y - - closedPositionOffset: favoriteStrip.height - - headerItem: Loader { sourceComponent: headerComponent } - headerHeight: root.headerHeight - } - } - - Component { - id: gridViewDrawer - HomeScreenComponents.GridViewAppDrawer { - anchors.fill: parent - topPadding: plasmoid.availableScreenRect.y - - // pad for navbar - rightPadding: MobileShell.TaskPanelControls.isPortrait ? 0 : MobileShell.TaskPanelControls.panelWidth - bottomPadding: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y - - closedPositionOffset: favoriteStrip.height - - headerItem: Loader { sourceComponent: headerComponent } - headerHeight: root.headerHeight - } - } - - Loader { - id: appDrawerLoader - anchors.fill: parent - sourceComponent: appDrawerType === "gridview" ? gridViewDrawer : listViewDrawer - } -} diff --git a/containments/homescreen/package/contents/ui/main.qml b/containments/homescreen/package/contents/ui/main.qml index c14d3ca3..5cb7d009 100644 --- a/containments/homescreen/package/contents/ui/main.qml +++ b/containments/homescreen/package/contents/ui/main.qml @@ -28,7 +28,7 @@ FocusScope { if (!componentComplete) { return; } - HomeScreenComponents.ApplicationListModel.maxFavoriteCount = Math.max(4, Math.floor(Math.min(width, height) / homescreen.homeScreenContents.appletsLayout.cellWidth)); + HomeScreenComponents.ApplicationListModel.maxFavoriteCount = Math.max(4, Math.floor(Math.min(width, height) / homescreen.homeScreenContents.favoriteStrip.cellWidth)); } function triggerHomeScreen() { @@ -50,21 +50,22 @@ FocusScope { } function onResetHomeScreenPosition() { - homescreen.flickablePages.scrollToPage(0); - homescreen.appDrawer.close(); + homescreen.homeScreenState.goToPageIndex(0); + homescreen.homeScreenState.closeAppDrawer(); } function onSnapHomeScreenPosition() { if (lastRequestedPosition < 0) { - homescreen.appDrawer.open(); + homescreen.homeScreenState.openAppDrawer(); } else { - homescreen.appDrawer.close(); + homescreen.homeScreenState.closeAppDrawer(); } } function onRequestRelativeScroll(pos) { - homescreen.appDrawer.offset -= pos.y; - lastRequestedPosition = pos.y; + // TODO + //homescreen.appDrawer.offset -= pos.y; + //lastRequestedPosition = pos.y; } function onOpenAppAnimation(splashIcon, title, x, y, sourceIconSize) { @@ -110,14 +111,29 @@ FocusScope { Plasmoid.onActivated: { console.log("Triggered!", plasmoid.nativeInterface.showingDesktop) - homescreen.activate(); + + // there's a couple of steps: + // - minimize windows + // - open app drawer + // - restore windows + if (!plasmoid.nativeInterface.showingDesktop) { + plasmoid.nativeInterface.showingDesktop = true; + } else if (homescreen.homeScreenState.currentView === MobileShell.HomeScreenState.PageView) { + homescreen.homeScreenState.openAppDrawer() + } else { + plasmoid.nativeInterface.showingDesktop = false + homescreen.homeScreenState.closeAppDrawer() + } } // homescreen component - HomeScreen { + HomeScreenComponents.HomeScreen { id: homescreen anchors.fill: parent - + + // make the homescreen not interactable when task switcher or startup feedback is on + interactive: !taskSwitcher.visible && !startupFeedback.visible + opacity: 1 NumberAnimation on opacity { id: opacityAnimation