diff --git a/components/mobilehomescreencomponents/qml/AbstractAppDrawer.qml b/components/mobilehomescreencomponents/qml/AbstractAppDrawer.qml new file mode 100644 index 00000000..07ecbb51 --- /dev/null +++ b/components/mobilehomescreencomponents/qml/AbstractAppDrawer.qml @@ -0,0 +1,258 @@ +/* + * SPDX-FileCopyrightText: 2021 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.15 as Controls + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PC3 +import org.kde.plasma.extras 2.0 as PlasmaExtra +import org.kde.kirigami 2.10 as Kirigami + +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 + +import "private" + +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 + + property real leftPadding: 0 + property real topPadding: 0 + property real bottomPadding: 100 + property real rightPadding: 0 + + property alias flickable: view + + property var contentItem + property real contentWidth: holdingColumn.width + + required property int headerHeight + required property var headerItem + + signal launched + signal dragStarted + + readonly property int reservedSpaceForLabel: metrics.height + property int availableCellHeight: PlasmaCore.Units.iconSizes.huge + reservedSpaceForLabel + + readonly property real openFactor: factorNormalize(flickable.contentY / (units.gridUnit * 10)) + + // height from top of screen that the drawer starts + readonly property real drawerTopMargin: height - topPadding - bottomPadding - closedPositionOffset + +//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)); + } + +//END functions + + 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" + visible: false + font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9 + } + + // bottom divider + GradientBar { + opacity: root.status !== AbstractAppDrawer.Status.Closed ? 0.6 : 0 + visible: root.bottomPadding > 0 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: root.bottomPadding - height + } + + // physical position of drawer is handled through this flickable + Flickable { + 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 + + 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 + spacing: 0 + + // margin of the drawer from the top + Rectangle { + id: topMargin + color: "transparent" + Layout.fillWidth: true + Layout.preferredHeight: root.drawerTopMargin + + OpenDrawerButton { + id: openDrawerButton + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + factor: root.openFactor + flickable: view + onOpenRequested: root.open(); + onCloseRequested: root.close(); + } + } + + // actual drawer + Controls.Control { + 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 + + // drawer background + background: Rectangle { + id: scrim + color: "black" + opacity: 0.6 * root.openFactor + + // remove radius + radius: view.contentY > (topMargin.height - PlasmaCore.Units.gridUnit) ? 0 : PlasmaCore.Units.gridUnit + Behavior on radius { + NumberAnimation { duration: Kirigami.Units.shortDuration; easing.type: Easing.InOutQuad } + } + } + + opacity: root.openFactor + + contentItem: ColumnLayout { + id: holdingColumn + width: view.width + spacing: 0 + + // drawer header + Controls.Control { + id: flickableHeader + Layout.preferredHeight: root.headerHeight + Layout.fillWidth: true + leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0 + + contentItem: root.headerItem + } + + // drawer body + Controls.Control { + id: flickableBody + Layout.fillHeight: true + Layout.fillWidth: true + leftPadding: 0; rightPadding: 0; topPadding: 0; bottomPadding: 0 + + contentItem: root.contentItem + } + } + } + } + } +} + diff --git a/components/mobilehomescreencomponents/qml/AppDrawer.qml b/components/mobilehomescreencomponents/qml/AppDrawer.qml deleted file mode 100644 index e96f7a98..00000000 --- a/components/mobilehomescreencomponents/qml/AppDrawer.qml +++ /dev/null @@ -1,306 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 Marco Martin - * - * SPDX-License-Identifier: GPL-2.0-or-later - */ - -import QtQuick 2.14 -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 PC3 -//import org.kde.kquickcontrolsaddons 2.0 -import org.kde.kirigami 2.10 as Kirigami - -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 - -import "private" - -Item { - id: root - - enum Status { - Closed, - Peeking, - Open - } - - enum MovementDirection { - None = 0, - Up, - Down - } - - readonly property int status: { - if (view.contentY >= -view.originY - view.height) { - return AppDrawer.Status.Open; - } else if (view.contentY > -view.originY - view.height*2 + closedPositionOffset*2) { - return AppDrawer.Status.Peeking; - } else { - return AppDrawer.Status.Closed; - } - } - - property real offset: 0 - property real closedPositionOffset: 0 - - property real leftPadding: 0 - property real topPadding: 0 - property real bottomPadding: 100 - property real rightPadding: 0 - - readonly property int columns: Math.floor(view.width / cellWidth) - property alias cellWidth: view.cellWidth - property alias cellHeight: view.cellHeight - signal launched - signal dragStarted - - readonly property int reservedSpaceForLabel: metrics.height - property int availableCellHeight: PlasmaCore.Units.iconSizes.huge + reservedSpaceForLabel - - property alias flickable: view - - readonly property real openFactor: Math.min(1, Math.max(0, Math.min(1, (view.contentY + view.originY + view.height*2 - root.closedPositionOffset*2) / (PlasmaCore.Units.gridUnit * 10)))) - - function open() { - if (root.status === AppDrawer.Status.Open) { - view.flick(0,1); - } else { - scrollAnim.to = 0 - scrollAnim.restart(); - } - } - - function close() { - if (root.status !== AppDrawer.Status.Closed) { - scrollAnim.to = -view.height + closedPositionOffset; - scrollAnim.restart(); - } - } - - function snapDrawerStatus() { - if (root.status !== AppDrawer.Status.Peeking) { - return; - } - - if (view.movementDirection === AppDrawer.MovementDirection.Up) { - if (view.contentY > 7 * -view.height / 8) { // over one eighth of the screen - open(); - } else { - close(); - } - } else { - if (view.contentY < -view.height / 8) { // over one eighth of the screen - close(); - } else { - open(); - } - } - } - - Drag.dragType: Drag.Automatic - - onOffsetChanged: { - if (!view.moving) { - view.contentY = Math.max(0, offset) - view.originY - view.height*2 + closedPositionOffset*2 - } - } - - NumberAnimation { - id: scrollAnim - target: view - properties: "contentY" - duration: PlasmaCore.Units.longDuration * 2 - easing.type: Easing.OutQuad - easing.amplitude: 2.0 - } - - PC3.Label { - id: metrics - text: "M\nM" - visible: false - font.pointSize: PlasmaCore.Theme.defaultFont.pointSize * 0.9 - } - - OpenDrawerButton { - id: openDrawerButton - anchors { - left: parent.left - right: parent.right - bottom: scrim.top - } - factor: root.openFactor - flickable: view - onOpenRequested: root.open(); - onCloseRequested: root.close(); - } - - Rectangle { - id: scrim - anchors { - left: view.left - right: view.right - leftMargin: -1 - rightMargin: -1 - } - border.color: Qt.rgba(1, 1, 1, 0.5) - radius: PlasmaCore.Units.gridUnit - color: "black" - opacity: 0.4 * root.openFactor - height: root.height + radius * 2 - y: Math.min(view.height, Math.max(-radius, -view.contentY - view.originY - root.height + root.topPadding + root.bottomPadding + root.closedPositionOffset)) - } - - Timer { - id: closeTimer - interval: 1000 - onTriggered: root.close(); - } - GridView { - id: view - anchors { - fill: parent - leftMargin: root.leftPadding - topMargin: root.topPadding - rightMargin: root.rightPadding - bottomMargin: root.bottomPadding - } - - opacity: { - if (root.status == AppDrawer.Status.Open) { - return 1; - } else if (root.status == AppDrawer.Status.Closed) { - return 0; - } else { // peeking - return root.openFactor; - } - } - - visible: root.status !== AppDrawer.Status.Closed - cellWidth: view.width / Math.floor(view.width / ((root.availableCellHeight - root.reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4)) - cellHeight: root.availableCellHeight - clip: true - - cacheBuffer: contentHeight - - property real oldContentY: contentY - property int movementDirection: AppDrawer.MovementDirection.None - onContentYChanged: { - if (contentY > oldContentY) { - movementDirection = AppDrawer.MovementDirection.Up; - } else { - movementDirection = AppDrawer.MovementDirection.Down; - } - - oldContentY = contentY; - root.offset = contentY + view.originY + view.height*2 - root.closedPositionOffset*2 - } - onMovementEnded: root.snapDrawerStatus() - onFlickEnded: movementEnded() - - // boundsBehavior: Flickable.StopAtBounds - - Connections { - target: HomeScreenComponents.ApplicationListModel - onLaunchError: NanoShell.StartupFeedback.close() - } - - model: HomeScreenComponents.ApplicationListModel - - header: Rectangle { - height: root.height - root.topPadding - root.bottomPadding - root.closedPositionOffset - property real oldHeight: height - onHeightChanged: { - if (root.status !== AppDrawer.Status.Open) { - view.contentY = -view.height + root.closedPositionOffset; - } - oldHeight = height; - } - } - - delegate: DrawerDelegate { - id: delegate - width: view.cellWidth - height: view.cellHeight - reservedSpaceForLabel: root.reservedSpaceForLabel - - onDragStarted: (imageSource, x, y, mimeData) => { - root.Drag.imageSource = imageSource; - root.Drag.hotSpot.x = x; - root.Drag.hotSpot.y = y; - root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData }; - - root.close() - - root.dragStarted() - root.Drag.active = true; - } - onLaunch: (x, y, icon, title, storageId) => { - if (icon !== "") { - NanoShell.StartupFeedback.open( - 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)); - } - - HomeScreenComponents.ApplicationListModel.setMinimizedDelegate(index, delegate); - HomeScreenComponents.ApplicationListModel.runApplication(storageId); - root.launched(); - closeTimer.restart(); - } - } - - PC3.ScrollBar.vertical: PC3.ScrollBar { - id: scrollabr - opacity: view.moving - interactive: false - enabled: false - Behavior on opacity { - OpacityAnimator { - duration: PlasmaCore.Units.longDuration * 2 - easing.type: Easing.InOutQuad - } - } - implicitWidth: Math.round(PlasmaCore.Units.gridUnit/3) - contentItem: Rectangle { - radius: width/2 - color: Qt.rgba(1, 1, 1, 0.3) - border.color: Qt.rgba(0, 0, 0, 0.4) - } - } - } - - Rectangle { - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - leftMargin: PlasmaCore.Units.gridUnit + root.leftPadding - rightMargin: PlasmaCore.Units.gridUnit + root.rightPadding - bottomMargin: root.bottomPadding - height - } - height: 1 - visible: root.bottomPadding > 0 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0) } - GradientStop { position: 0.15; color: Qt.rgba(1, 1, 1, 0.5) } - GradientStop { position: 0.5; color: Qt.rgba(1, 1, 1, 1) } - GradientStop { position: 0.85; color: Qt.rgba(1, 1, 1, 0.5) } - GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0) } - } - opacity: root.status !== AppDrawer.Status.Closed ? 0.6 : 0 - Behavior on opacity { - OpacityAnimator { - duration: PlasmaCore.Units.longDuration * 2 - easing.type: Easing.InOutQuad - } - } - } -} diff --git a/components/mobilehomescreencomponents/qml/DrawerGridDelegate.qml b/components/mobilehomescreencomponents/qml/DrawerGridDelegate.qml new file mode 100644 index 00000000..3f232d10 --- /dev/null +++ b/components/mobilehomescreencomponents/qml/DrawerGridDelegate.qml @@ -0,0 +1,103 @@ +/* + * 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 2.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) + }) + } + + onClicked: { + 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: 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 + maximumLineCount: 2 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignTop + elide: Text.ElideRight + + text: model.applicationName + + //FIXME: export smallestReadableFont + font.pointSize: theme.defaultFont.pointSize * 0.9 + color: "white" + } + } +} + diff --git a/components/mobilehomescreencomponents/qml/DrawerListDelegate.qml b/components/mobilehomescreencomponents/qml/DrawerListDelegate.qml new file mode 100644 index 00000000..18fc43cf --- /dev/null +++ b/components/mobilehomescreencomponents/qml/DrawerListDelegate.qml @@ -0,0 +1,108 @@ +/* + * SPDX-FileCopyrightText: 2019 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * 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 2.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 + 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) + }) + } + + onClicked: { + 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); + } + } + hoverEnabled: true + + Rectangle { + anchors.fill: parent + color: delegate.pressed ? Qt.rgba(255, 255, 255, 0.2) : (delegate.containsMouse ? Qt.rgba(255, 255, 255, 0.05) : "transparent") + Behavior on color { + ColorAnimation { duration: PlasmaCore.Units.shortDuration } + } + } + + RowLayout { + anchors { + fill: parent + leftMargin: PlasmaCore.Units.smallSpacing * 2 + topMargin: PlasmaCore.Units.smallSpacing + rightMargin: PlasmaCore.Units.smallSpacing * 2 + bottomMargin: PlasmaCore.Units.smallSpacing + } + spacing: 0 + + PlasmaCore.IconItem { + id: icon + + Layout.alignment: Qt.AlignLeft + Layout.minimumWidth: Layout.minimumHeight + Layout.preferredWidth: Layout.minimumHeight + Layout.minimumHeight: parent.height + 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: theme.highlightColor + } + } + + PlasmaComponents.Label { + id: label + visible: text.length > 0 + + Layout.fillWidth: true + wrapMode: Text.WordWrap + Layout.leftMargin: PlasmaCore.Units.smallSpacing * 2 + Layout.rightMargin: PlasmaCore.Units.largeSpacing + maximumLineCount: 1 + elide: Text.ElideRight + + text: model.applicationName + + //FIXME: export smallestReadableFont + font.pointSize: Math.round(theme.defaultFont.pointSize * 1.1) + color: "white" + } + } +} + + diff --git a/components/mobilehomescreencomponents/qml/FlickablePages.qml b/components/mobilehomescreencomponents/qml/FlickablePages.qml index 9c5015e3..7ca888e6 100644 --- a/components/mobilehomescreencomponents/qml/FlickablePages.qml +++ b/components/mobilehomescreencomponents/qml/FlickablePages.qml @@ -24,7 +24,7 @@ import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenCompon Flickable { id: mainFlickable - property AppDrawer appDrawer + property AbstractAppDrawer appDrawer readonly property int totalPages: Math.ceil(contentWidth / width) property int currentIndex: 0 @@ -35,9 +35,8 @@ Flickable { property alias dragGestureEnabled: gestureHandler.enabled opacity: appDrawer ? 1 - appDrawer.openFactor : 1 transform: Translate { - y: appDrawer ? -mainFlickable.height/10 * appDrawer.openFactor : 0 + y: appDrawer ? (-mainFlickable.height / 20) * appDrawer.openFactor : 0 } - scale: appDrawer ? (3 - appDrawer.openFactor) /3 : 1 clip: true property bool showAddPageIndicator: false diff --git a/components/mobilehomescreencomponents/qml/GridViewAppDrawer.qml b/components/mobilehomescreencomponents/qml/GridViewAppDrawer.qml new file mode 100644 index 00000000..f86d8d36 --- /dev/null +++ b/components/mobilehomescreencomponents/qml/GridViewAppDrawer.qml @@ -0,0 +1,120 @@ +/* + * SPDX-FileCopyrightText: 2021 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.15 as Controls + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PC3 +import org.kde.plasma.extras 2.0 as PlasmaExtra +import org.kde.kirigami 2.10 as Kirigami + +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 + +import "private" + +AbstractAppDrawer { + id: root + + 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; + } + } + + cellWidth: root.contentWidth / Math.floor(root.contentWidth / ((root.availableCellHeight - root.reservedSpaceForLabel) + PlasmaCore.Units.smallSpacing*4)) + cellHeight: root.availableCellHeight + + property int columns: Math.floor(root.contentWidth / cellWidth) + property int rows: Math.ceil(model.count / columns) + cacheBuffer: rows * cellHeight + + model: HomeScreenComponents.ApplicationListModel + + delegate: DrawerGridDelegate { + id: delegate + + width: gridView.cellWidth + height: gridView.cellHeight + reservedSpaceForLabel: root.reservedSpaceForLabel + + onDragStarted: (imageSource, x, y, mimeData) => { + root.Drag.imageSource = imageSource; + root.Drag.hotSpot.x = x; + root.Drag.hotSpot.y = y; + root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData }; + + root.close() + + root.dragStarted() + root.Drag.active = true; + } + onLaunch: (x, y, icon, title, storageId) => { + if (icon !== "") { + NanoShell.StartupFeedback.open( + 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)); + } + + HomeScreenComponents.ApplicationListModel.setMinimizedDelegate(index, delegate); + HomeScreenComponents.ApplicationListModel.runApplication(storageId); + root.launched(); + closeTimer.restart(); + } + } + + PC3.ScrollBar.vertical: PC3.ScrollBar { + id: scrollBar + interactive: true + enabled: true + Behavior on opacity { + OpacityAnimator { + duration: PlasmaCore.Units.longDuration * 2 + easing.type: Easing.InOutQuad + } + } + implicitWidth: PlasmaCore.Units.smallSpacing + contentItem: Rectangle { + radius: width/2 + color: Qt.rgba(1, 1, 1, 0.3) + } + } + } +} + diff --git a/components/mobilehomescreencomponents/qml/HomeScreenContents.qml b/components/mobilehomescreencomponents/qml/HomeScreenContents.qml index 63b3b575..94d8857b 100644 --- a/components/mobilehomescreencomponents/qml/HomeScreenContents.qml +++ b/components/mobilehomescreencomponents/qml/HomeScreenContents.qml @@ -160,7 +160,7 @@ DragDrop.DropArea { TapHandler { target: mainFlickable - enabled: appDrawer.status !== AppDrawer.Status.Open + enabled: appDrawer.status !== AbstractAppDrawer.Status.Open onTapped: { //Hides icons close button appletsLayout.appletsLayoutInteracted(); diff --git a/components/mobilehomescreencomponents/qml/ListViewAppDrawer.qml b/components/mobilehomescreencomponents/qml/ListViewAppDrawer.qml new file mode 100644 index 00000000..56f9698d --- /dev/null +++ b/components/mobilehomescreencomponents/qml/ListViewAppDrawer.qml @@ -0,0 +1,116 @@ +/* + * SPDX-FileCopyrightText: 2021 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.15 as Controls + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 3.0 as PC3 +import org.kde.plasma.extras 2.0 as PlasmaExtra +import org.kde.kirigami 2.10 as Kirigami + +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 + +import "private" + +AbstractAppDrawer { + id: root + + contentItem: ListView { + id: listView + clip: true + 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; + } + } + + property int delegateHeight: PlasmaCore.Units.gridUnit * 3 + + model: HomeScreenComponents.ApplicationListModel + + delegate: DrawerListDelegate { + id: delegate + + width: listView.width + height: listView.delegateHeight + reservedSpaceForLabel: root.reservedSpaceForLabel + + onDragStarted: (imageSource, x, y, mimeData) => { + root.Drag.imageSource = imageSource; + root.Drag.hotSpot.x = x; + root.Drag.hotSpot.y = y; + root.Drag.mimeData = { "text/x-plasma-phone-homescreen-launcher": mimeData }; + + root.close() + + root.dragStarted() + root.Drag.active = true; + } + onLaunch: (x, y, icon, title, storageId) => { + if (icon !== "") { + NanoShell.StartupFeedback.open( + 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)); + } + + HomeScreenComponents.ApplicationListModel.setMinimizedDelegate(index, delegate); + HomeScreenComponents.ApplicationListModel.runApplication(storageId); + root.launched(); + closeTimer.restart(); + } + } + + PC3.ScrollBar.vertical: PC3.ScrollBar { + id: scrollBar + interactive: true + enabled: true + Behavior on opacity { + OpacityAnimator { + duration: PlasmaCore.Units.longDuration * 2 + easing.type: Easing.InOutQuad + } + } + implicitWidth: PlasmaCore.Units.smallSpacing + contentItem: Rectangle { + radius: width/2 + color: Qt.rgba(1, 1, 1, 0.3) + } + } + } +} diff --git a/components/mobilehomescreencomponents/qml/private/DragGestureHandler.qml b/components/mobilehomescreencomponents/qml/private/DragGestureHandler.qml index 596cfa28..0655795c 100644 --- a/components/mobilehomescreencomponents/qml/private/DragGestureHandler.qml +++ b/components/mobilehomescreencomponents/qml/private/DragGestureHandler.qml @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2021 Marco Martin + * SPDX-FileCopyrightText: 2021 Devin Lin * * SPDX-License-Identifier: LGPL-2.0-or-later */ @@ -15,7 +16,7 @@ DragHandler { yAxis.enabled: enabled xAxis.enabled: enabled property Flickable mainFlickable - property Launcher.AppDrawer appDrawer + property Launcher.AbstractAppDrawer appDrawer signal snapPage signal snapNextPage signal snapPrevPage @@ -33,7 +34,7 @@ DragHandler { if (active) { if (root.appDrawer) { if (__scrollDirection === DragGestureHandler.None) { - if (root.appDrawer.offset > PlasmaCore.Units.gridUnit) { + if (root.appDrawer.flickable.contentY > PlasmaCore.Units.gridUnit * 2) { __scrollDirection = DragGestureHandler.Vertical; snapPage(); @@ -49,7 +50,7 @@ DragHandler { } if (__scrollDirection !== DragGestureHandler.Left && __scrollDirection !== DragGestureHandler.Right) { - root.appDrawer.offset = -translation.y; + root.appDrawer.flickable.contentY = Math.min(root.appDrawer.drawerTopMargin, Math.max(0, -translation.y)); } } if (__scrollDirection !== DragGestureHandler.Vertical) { diff --git a/components/mobilehomescreencomponents/qml/private/GradientBar.qml b/components/mobilehomescreencomponents/qml/private/GradientBar.qml new file mode 100644 index 00000000..630aa332 --- /dev/null +++ b/components/mobilehomescreencomponents/qml/private/GradientBar.qml @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021 Marco Martin + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import QtQuick 2.15 +import org.kde.plasma.core 2.0 as PlasmaCore + +Item { + Behavior on opacity { + OpacityAnimator { + duration: PlasmaCore.Units.longDuration * 2 + easing.type: Easing.InOutQuad + } + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + leftMargin: PlasmaCore.Units.gridUnit + root.leftPadding + rightMargin: PlasmaCore.Units.gridUnit + root.rightPadding + } + height: 1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: Qt.rgba(1, 1, 1, 0) } + GradientStop { position: 0.15; color: Qt.rgba(1, 1, 1, 0.5) } + GradientStop { position: 0.5; color: Qt.rgba(1, 1, 1, 1) } + GradientStop { position: 0.85; color: Qt.rgba(1, 1, 1, 0.5) } + GradientStop { position: 1.0; color: Qt.rgba(1, 1, 1, 0) } + } + } +} diff --git a/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml b/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml index 15397000..4e8d497a 100644 --- a/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml +++ b/components/mobilehomescreencomponents/qml/private/OpenDrawerButton.qml @@ -25,23 +25,20 @@ MouseArea { property Flickable flickable property real factor: 0 - height: PlasmaCore.Units.iconSizes.medium + cursorShape: Qt.PointingHandCursor + height: PlasmaCore.Units.iconSizes.smallMedium signal openRequested signal closeRequested - + onClicked: { - if ((arrowUpIcon.flickable.contentY + arrowUpIcon.flickable.originY + arrowUpIcon.flickable.height*2) >= arrowUpIcon.flickable.height/2) { - closeRequested(); - } else { - openRequested(); - } + openRequested(); scrollAnim.restart(); } Item { anchors.centerIn: parent - width: PlasmaCore.Units.iconSizes.medium + width: PlasmaCore.Units.iconSizes.smallMedium height: width Rectangle { @@ -51,7 +48,7 @@ MouseArea { left: parent.left verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor } - color: PlasmaCore.Theme.backgroundColor + color: "white" transformOrigin: Item.Right rotation: -45 + 90 * arrowUpIcon.factor antialiasing: true @@ -64,7 +61,7 @@ MouseArea { right: parent.right verticalCenterOffset: -arrowUpIcon.height/4 + (arrowUpIcon.height/4) * arrowUpIcon.factor } - color: PlasmaCore.Theme.backgroundColor + color: "white" transformOrigin: Item.Left rotation: 45 - 90 * arrowUpIcon.factor antialiasing: true diff --git a/components/mobilehomescreencomponents/qml/qmldir b/components/mobilehomescreencomponents/qml/qmldir index 62a8f72e..9a3407c5 100644 --- a/components/mobilehomescreencomponents/qml/qmldir +++ b/components/mobilehomescreencomponents/qml/qmldir @@ -2,8 +2,11 @@ module org.kde.plasma.private.mobilehomescreencomponents plugin mobilehomescreencomponentsplugin -AppDrawer 0.1 AppDrawer.qml -DrawerDelegate 0.1 DrawerDelegate.qml +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 FavoriteStrip 0.1 FavoriteStrip.qml FlickablePages 0.1 FlickablePages.qml HomeDelegate 0.1 HomeDelegate.qml diff --git a/containments/homescreen/package/contents/ui/main.qml b/containments/homescreen/package/contents/ui/main.qml index 75564a42..8232851e 100644 --- a/containments/homescreen/package/contents/ui/main.qml +++ b/containments/homescreen/package/contents/ui/main.qml @@ -12,6 +12,7 @@ import QtGraphicalEffects 1.0 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.extras 2.0 as PlasmaExtra import org.kde.draganddrop 2.0 as DragDrop import org.kde.plasma.private.mobilehomescreencomponents 0.1 as HomeScreenComponents @@ -53,6 +54,9 @@ FocusScope { } componentComplete = true; recalculateMaxFavoriteCount() + + // ensure the gestures work immediately on load + forceActiveFocus(); } Plasmoid.onScreenChanged: { @@ -76,13 +80,13 @@ FocusScope { } function onSnapHomeScreenPosition() { if (lastRequestedPosition < 0) { - appDrawer.open(); + root.appDrawer.open(); } else { - appDrawer.close(); + root.appDrawer.close(); } } function onRequestRelativeScroll(pos) { - appDrawer.offset -= pos.y; + root.appDrawer.offset -= pos.y; lastRequestedPosition = pos.y; } } @@ -96,23 +100,45 @@ FocusScope { bottomMargin: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y } - //TODO: favorite strip disappearing with everything else - footer: favoriteStrip appletsLayout: homeScreenContents.appletsLayout - appDrawer: appDrawer + 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.focus && appDrawer.status !== HomeScreenComponents.AppDrawer.Status.Open && !appletsLayout.editMode && !plasmoid.editMode && !homeScreenContents.launcherDragManager.active + dragGestureEnabled: root.focus && (!appDrawer || appDrawer.status !== HomeScreenComponents.AbstractAppDrawer.Status.Open) && !appletsLayout.editMode && !plasmoid.editMode && !homeScreenContents.launcherDragManager.active HomeScreenComponents.HomeScreenContents { id: homeScreenContents width: mainFlickable.width * 100 favoriteStrip: favoriteStrip } + + footer: HomeScreenComponents.FavoriteStrip { + id: favoriteStrip + + appletsLayout: homeScreenContents.appletsLayout + visible: 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.focus = true; + } + } } + // listview/gridview header + property int headerHeight: Math.round(PlasmaCore.Units.gridUnit * 3) + property string appDrawerType: "gridview" // gridview/listview + property alias appDrawer: appDrawerLoader.item + Plasmoid.onActivated: { console.log("Triggered!", plasmoid.nativeInterface.showingDesktop) @@ -124,41 +150,90 @@ FocusScope { plasmoid.nativeInterface.showingDesktop = true } else if (appDrawer.status !== HomeScreenComponents.AppDrawer.Status.Open) { mainFlickable.currentIndex = 0 - appDrawer.open() + root.appDrawer.open() } else { plasmoid.nativeInterface.showingDesktop = false - appDrawer.close() + root.appDrawer.close() } } - HomeScreenComponents.AppDrawer { - id: appDrawer - anchors.fill: parent - - topPadding: plasmoid.availableScreenRect.y - bottomPadding: plasmoid.screenGeometry.height - plasmoid.availableScreenRect.height - plasmoid.availableScreenRect.y - rightPadding: plasmoid.screenGeometry.width - plasmoid.availableScreenRect.width - plasmoid.availableScreenRect.x - closedPositionOffset: favoriteStrip.height - } - - HomeScreenComponents.FavoriteStrip { - id: favoriteStrip - - appletsLayout: homeScreenContents.appletsLayout - - visible: 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; + + Component { + id: headerComponent + PlasmaCore.ColorScope { + colorGroup: PlasmaCore.Theme.ComplementaryColorGroup + + RowLayout { + anchors.topMargin: PlasmaCore.Units.smallSpacing + anchors.leftMargin: PlasmaCore.Units.largeSpacing + anchors.rightMargin: PlasmaCore.Units.largeSpacing + anchors.fill: parent + spacing: PlasmaCore.Units.smallSpacing + + PlasmaExtra.Heading { + color: "white" + level: 1 + text: i18n("Applications") + } + Item { Layout.fillWidth: true } + PlasmaComponents.ToolButton { + icon.name: "view-list-symbolic" + implicitWidth: Math.round(PlasmaCore.Units.gridUnit * 2.1) + implicitHeight: Math.round(PlasmaCore.Units.gridUnit * 2.1) + onClicked: { + if (root.appDrawerType !== "listview") { + root.appDrawerType = "listview"; + appDrawer.flickable.goToBeginning(); // jump to top + } + } + } + PlasmaComponents.ToolButton { + icon.name: "view-grid-symbolic" + implicitWidth: Math.round(PlasmaCore.Units.gridUnit * 2.1) + implicitHeight: Math.round(PlasmaCore.Units.gridUnit * 2.1) + onClicked: { + if (root.appDrawerType !== "gridview") { + root.appDrawerType = "gridview"; + appDrawer.flickable.goToBeginning(); // jump to top + } + } + } } - onLongPressed: homeScreenContents.appletsLayout.editMode = true; - onPressedChanged: root.focus = true; } } + + Component { + id: listViewDrawer + HomeScreenComponents.ListViewAppDrawer { + anchors.fill: parent + topPadding: plasmoid.availableScreenRect.y + 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 + 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 + } }