From 564c3f9e818cc722933f055ce108a5b1e8adfd36 Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Thu, 30 Jun 2022 00:40:22 -0400 Subject: [PATCH] homescreens/halcyon: Add initial folders implementation --- .../homescreens/halcyon/applicationfolder.cpp | 32 ++++ .../homescreens/halcyon/applicationfolder.h | 4 + .../contents/ui/FavoritesAppDelegate.qml | 181 ++++++++++++------ .../package/contents/ui/FavoritesGrid.qml | 32 ++-- .../package/contents/ui/FavoritesView.qml | 170 ++++++++++++++++ .../package/contents/ui/FolderGrid.qml | 168 ++++++++++++++++ .../package/contents/ui/HomeScreen.qml | 7 +- 7 files changed, 516 insertions(+), 78 deletions(-) create mode 100644 containments/homescreens/halcyon/package/contents/ui/FavoritesView.qml create mode 100644 containments/homescreens/halcyon/package/contents/ui/FolderGrid.qml diff --git a/containments/homescreens/halcyon/applicationfolder.cpp b/containments/homescreens/halcyon/applicationfolder.cpp index cfe6517e..1b56da15 100644 --- a/containments/homescreens/halcyon/applicationfolder.cpp +++ b/containments/homescreens/halcyon/applicationfolder.cpp @@ -54,6 +54,16 @@ void ApplicationFolder::setName(QString &name) Q_EMIT saveRequested(); } +QList ApplicationFolder::appPreviews() +{ + QList previews; + // we give a maximum of 4 icons + for (int i = 0; i < std::min(m_applications.length(), 4); ++i) { + previews.push_back(m_applications[i]); + } + return previews; +} + QList ApplicationFolder::applications() { return m_applications; @@ -66,6 +76,28 @@ void ApplicationFolder::setApplications(QList applications) Q_EMIT saveRequested(); } +void ApplicationFolder::moveEntry(int fromRow, int toRow) +{ + if (fromRow < 0 || toRow < 0 || fromRow >= m_applications.length() || toRow >= m_applications.length() || fromRow == toRow) { + return; + } + if (toRow > fromRow) { + ++toRow; + } + + if (toRow > fromRow) { + Application *app = m_applications.at(fromRow); + m_applications.insert(toRow, app); + m_applications.takeAt(fromRow); + + } else { + Application *app = m_applications.takeAt(fromRow); + m_applications.insert(toRow, app); + } + Q_EMIT applicationsChanged(); + Q_EMIT saveRequested(); +} + void ApplicationFolder::addApp(const QString &storageId, int row) { if (row < 0 || row > m_applications.size()) { diff --git a/containments/homescreens/halcyon/applicationfolder.h b/containments/homescreens/halcyon/applicationfolder.h index b817c343..bd372e3a 100644 --- a/containments/homescreens/halcyon/applicationfolder.h +++ b/containments/homescreens/halcyon/applicationfolder.h @@ -22,6 +22,7 @@ class ApplicationFolder : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QList appPreviews READ appPreviews NOTIFY applicationsChanged) Q_PROPERTY(QList applications READ applications NOTIFY applicationsChanged) public: @@ -33,9 +34,12 @@ public: QString name() const; void setName(QString &name); + QList appPreviews(); + QList applications(); void setApplications(QList applications); + Q_INVOKABLE void moveEntry(int fromRow, int toRow); Q_INVOKABLE void addApp(const QString &storageId, int row); Q_INVOKABLE void removeApp(int row); diff --git a/containments/homescreens/halcyon/package/contents/ui/FavoritesAppDelegate.qml b/containments/homescreens/halcyon/package/contents/ui/FavoritesAppDelegate.qml index ce6d41e0..b01df3d9 100644 --- a/containments/homescreens/halcyon/package/contents/ui/FavoritesAppDelegate.qml +++ b/containments/homescreens/halcyon/package/contents/ui/FavoritesAppDelegate.qml @@ -19,58 +19,61 @@ import org.kde.kirigami 2.19 as Kirigami MouseArea { id: delegate - property int visualIndex: 0 property real leftPadding property real rightPadding - property alias iconItem: icon - property var application + // whether this delegate is a folder + property bool isFolder + // folder object + property var folder + readonly property string folderName: folder ? folder.name : "" + + // app object + property var application readonly property string applicationName: application ? application.name : "" readonly property string applicationStorageId: application ? application.storageId : "" readonly property string applicationIcon: application ? application.icon : "" - signal launch(int x, int y, var source, string title, string storageId) - signal dragStarted(string imageSource, int x, int y, string mimeData) - - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - - onLaunch: { - if (icon !== "") { - MobileShell.HomeScreenControls.openAppLaunchAnimation( - 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)); - } - - application.setMinimizedDelegate(delegate); - application.runApplication(); - } + signal folderOpenRequested() function openContextMenu() { dialogLoader.active = true; dialogLoader.item.open(); } - function launchApp() { - if (application.running) { - delegate.launch(0, 0, "", applicationName, applicationStorageId); + function launch() { + if (isFolder) { + folderOpenRequested(); } else { - delegate.launch(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), icon.source, applicationName, applicationStorageId); + if (application.running) { + launchAppWithAnim(0, 0, "", applicationName, applicationStorageId); + } else { + launchAppWithAnim(delegate.x + (PlasmaCore.Units.smallSpacing * 2), delegate.y + (PlasmaCore.Units.smallSpacing * 2), iconLoader.source, applicationName, applicationStorageId); + } } } + function launchAppWithAnim(x: int, y: int, source, title: string, storageId: string) { + if (source !== "") { + MobileShell.HomeScreenControls.openAppLaunchAnimation( + source, + title, + iconLoader.Kirigami.ScenePosition.x + iconLoader.width/2, + iconLoader.Kirigami.ScenePosition.y + iconLoader.height/2, + Math.min(iconLoader.width, iconLoader.height)); + } + + application.setMinimizedDelegate(delegate); + application.runApplication(); + } + property bool inDrag: false acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: (mouse.button === Qt.RightButton) ? openContextMenu() : launchApp(); + onClicked: (mouse.button === Qt.RightButton) ? openContextMenu() : launch(); onReleased: { parent.Drag.drop(); inDrag = false; @@ -131,37 +134,15 @@ MouseArea { } spacing: 0 - PlasmaCore.IconItem { - id: icon - + Loader { + id: iconLoader Layout.alignment: Qt.AlignLeft Layout.minimumWidth: Layout.minimumHeight Layout.preferredWidth: Layout.minimumHeight Layout.minimumHeight: parent.height Layout.preferredHeight: Layout.minimumHeight - usesPlasmaTheme: false - source: delegate.applicationIcon - - Rectangle { - anchors { - horizontalCenter: parent.horizontalCenter - bottom: parent.bottom - } - visible: application ? application.running : false - radius: width - width: PlasmaCore.Units.smallSpacing - height: width - color: PlasmaCore.Theme.highlightColor - } - - layer.enabled: true - layer.effect: DropShadow { - verticalOffset: 1 - radius: 4 - samples: 6 - color: Qt.rgba(0, 0, 0, 0.5) - } + sourceComponent: delegate.isFolder ? folderIconComponent : appIconComponent } PlasmaComponents.Label { @@ -175,7 +156,7 @@ MouseArea { maximumLineCount: 1 elide: Text.ElideRight - text: delegate.applicationName + text: delegate.isFolder ? delegate.folderName : delegate.applicationName font.pointSize: PlasmaCore.Theme.defaultFont.pointSize font.weight: Font.Bold @@ -189,6 +170,96 @@ MouseArea { color: Qt.rgba(0, 0, 0, 0.5) } } + + Kirigami.Icon { + Layout.alignment: Qt.AlignRight + Layout.minimumWidth: Layout.minimumHeight + Layout.preferredWidth: Layout.minimumHeight + Layout.minimumHeight: Math.round(parent.height * 0.5) + Layout.preferredHeight: Layout.minimumHeight + + isMask: true + color: 'white' + source: 'arrow-right' + visible: delegate.isFolder + + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 1 + radius: 4 + samples: 6 + color: Qt.rgba(0, 0, 0, 0.5) + } + } + } + } + + Component { + id: appIconComponent + + PlasmaCore.IconItem { + usesPlasmaTheme: false + source: delegate.isFolder ? 'document-open-folder' : delegate.applicationIcon + + Rectangle { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + } + visible: application ? application.running : false + radius: width + width: PlasmaCore.Units.smallSpacing + height: width + color: PlasmaCore.Theme.highlightColor + } + + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 1 + radius: 4 + samples: 6 + color: Qt.rgba(0, 0, 0, 0.5) + } + } + } + + Component { + id: folderIconComponent + + Item { + Rectangle { + anchors.fill: parent + anchors.margins: PlasmaCore.Units.smallSpacing + color: Qt.rgba(255, 255, 255, 0.2) + radius: PlasmaCore.Units.smallSpacing + + Grid { + id: grid + anchors.fill: parent + anchors.margins: PlasmaCore.Units.smallSpacing + columns: 2 + spacing: PlasmaCore.Units.smallSpacing + + property var previews: model.folder.appPreviews + + Repeater { + model: grid.previews + delegate: Kirigami.Icon { + implicitWidth: (grid.width - PlasmaCore.Units.smallSpacing) / 2 + implicitHeight: (grid.width - PlasmaCore.Units.smallSpacing) / 2 + source: modelData.icon + + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 1 + radius: 4 + samples: 3 + color: Qt.rgba(0, 0, 0, 0.5) + } + } + } + } + } } } } diff --git a/containments/homescreens/halcyon/package/contents/ui/FavoritesGrid.qml b/containments/homescreens/halcyon/package/contents/ui/FavoritesGrid.qml index af86811a..68be3a03 100644 --- a/containments/homescreens/halcyon/package/contents/ui/FavoritesGrid.qml +++ b/containments/homescreens/halcyon/package/contents/ui/FavoritesGrid.qml @@ -18,20 +18,15 @@ import org.kde.phone.homescreen.halcyon 1.0 as Halcyon GridView { id: root - required property var searchWidget + signal openConfigureRequested() - - readonly property real twoColumnThreshold: PlasmaCore.Units.gridUnit * 10 - readonly property bool twoColumn: root.width / 2 > twoColumnThreshold - - cellWidth: twoColumn ? root.width / 2 : root.width - cellHeight: delegateHeight + signal requestOpenFolder(Halcyon.ApplicationFolder folder) // don't set anchors.margins since we want everywhere to be draggable - readonly property real leftMargin: Math.round(parent.width * 0.1) - readonly property real rightMargin: Math.round(parent.width * 0.1) - readonly property real delegateHeight: PlasmaCore.Units.gridUnit * 3 + required property real leftMargin + required property real rightMargin + required property bool twoColumn // search widget open gesture property bool openingSearchWidget: false @@ -63,7 +58,7 @@ GridView { } header: MobileShell.BaseItem { - topPadding: Math.round(swipeView.height * 0.2) + topPadding: Math.round(root.height * 0.2) bottomPadding: PlasmaCore.Units.largeSpacing leftPadding: root.leftMargin rightPadding: root.rightMargin @@ -82,8 +77,6 @@ GridView { delegate: DropArea { id: delegateRoot - property var application: model.application - property int modelIndex property int visualIndex: DelegateModel.itemsIndex @@ -106,12 +99,12 @@ GridView { FavoritesAppDelegate { id: appDelegate visualIndex: delegateRoot.visualIndex - application: delegateRoot.application - anchors { - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } + isFolder: model.isFolder + folder: model.folder + application: model.application + + onFolderOpenRequested: root.requestOpenFolder(model.folder) readonly property bool isLeftColumn: !root.twoColumn || ((visualIndex % 2) === 0) readonly property bool isRightColumn: !root.twoColumn || ((visualIndex % 2) !== 0) @@ -121,6 +114,9 @@ GridView { implicitWidth: root.cellWidth implicitHeight: visible ? root.cellHeight : 0 + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + states: [ State { when: appDelegate.drag.active diff --git a/containments/homescreens/halcyon/package/contents/ui/FavoritesView.qml b/containments/homescreens/halcyon/package/contents/ui/FavoritesView.qml new file mode 100644 index 00000000..cb6ddb82 --- /dev/null +++ b/containments/homescreens/halcyon/package/contents/ui/FavoritesView.qml @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: 2022 Devin Lin +// SPDX-License-Identifier: LGPL-2.0-or-later + +import QtQuick 2.12 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.1 +import QtQml.Models 2.15 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.components 3.0 as PC3 +import org.kde.draganddrop 2.0 as DragDrop + +import org.kde.kirigami 2.19 as Kirigami +import org.kde.plasma.private.mobileshell 1.0 as MobileShell +import org.kde.phone.homescreen.halcyon 1.0 as Halcyon + +Item { + id: root + + required property bool interactive + required property var searchWidget + + readonly property real twoColumnThreshold: PlasmaCore.Units.gridUnit * 10 + readonly property bool twoColumn: root.width / 2 > twoColumnThreshold + + readonly property real cellWidth: twoColumn ? root.width / 2 : root.width + readonly property real cellHeight: delegateHeight + + readonly property real leftMargin: Math.round(parent.width * 0.1) + readonly property real rightMargin: Math.round(parent.width * 0.1) + readonly property real delegateHeight: PlasmaCore.Units.gridUnit * 3 + + property bool folderShown: false + onFolderShownChanged: folderShown ? openFolderAnim.restart() : closeFolderAnim.restart() + + signal openConfigureRequested() + + FavoritesGrid { + id: favoritesGrid + anchors.fill: parent + interactive: root.interactive + searchWidget: root.searchWidget + + cellWidth: root.cellWidth + cellHeight: root.cellHeight + + leftMargin: root.leftMargin + rightMargin: root.rightMargin + twoColumn: root.twoColumn + + onOpenConfigureRequested: root.openConfigureRequested() + onRequestOpenFolder: (folder) => { + folderGrid.folder = folder; + root.folderShown = true; + } + + property real translateX: 0 + transform: Translate { x: favoritesGrid.translateX } + visible: opacity !== 0 + } + + FolderGrid { + id: folderGrid + anchors.fill: parent + folder: null + + interactive: root.interactive + + cellWidth: root.cellWidth + cellHeight: root.cellHeight + + leftMargin: root.leftMargin + rightMargin: root.rightMargin + twoColumn: root.twoColumn + + onOpenConfigureRequested: root.openConfigureRequested() + onCloseRequested: root.folderShown = false + + property real translateX: 0 + transform: Translate { x: folderGrid.translateX } + opacity: 0 + visible: opacity !== 0 + } + + SequentialAnimation { + id: openFolderAnim + + ParallelAnimation { + NumberAnimation { + target: favoritesGrid + properties: 'translateX' + duration: 200 + from: 0 + to: -PlasmaCore.Units.gridUnit + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: favoritesGrid + properties: 'opacity' + duration: 200 + from: 1 + to: 0 + easing.type: Easing.InOutQuad + } + } + + ParallelAnimation { + NumberAnimation { + target: folderGrid + properties: 'translateX' + duration: 200 + from: PlasmaCore.Units.gridUnit + to: 0 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: folderGrid + properties: 'opacity' + duration: 200 + from: 0 + to: 1 + easing.type: Easing.InOutQuad + } + } + } + + SequentialAnimation { + id: closeFolderAnim + + ParallelAnimation { + NumberAnimation { + target: folderGrid + properties: 'translateX' + duration: 200 + from: 0 + to: PlasmaCore.Units.gridUnit + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: folderGrid + properties: 'opacity' + duration: 200 + from: 1 + to: 0 + easing.type: Easing.InOutQuad + } + } + + ParallelAnimation { + NumberAnimation { + target: favoritesGrid + properties: 'translateX' + duration: 200 + from: -PlasmaCore.Units.gridUnit + to: 0 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: favoritesGrid + properties: 'opacity' + duration: 200 + from: 0 + to: 1 + easing.type: Easing.InOutQuad + } + } + } +} diff --git a/containments/homescreens/halcyon/package/contents/ui/FolderGrid.qml b/containments/homescreens/halcyon/package/contents/ui/FolderGrid.qml new file mode 100644 index 00000000..575fa3d9 --- /dev/null +++ b/containments/homescreens/halcyon/package/contents/ui/FolderGrid.qml @@ -0,0 +1,168 @@ +// SPDX-FileCopyrightText: 2022 Devin Lin +// SPDX-License-Identifier: LGPL-2.0-or-later + +import QtQuick 2.12 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.1 +import QtQml.Models 2.15 +import QtGraphicalEffects 1.12 + +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.components 3.0 as PC3 +import org.kde.draganddrop 2.0 as DragDrop + +import org.kde.kirigami 2.19 as Kirigami +import org.kde.plasma.private.mobileshell 1.0 as MobileShell +import org.kde.phone.homescreen.halcyon 1.0 as Halcyon + +GridView { + id: root + property Halcyon.ApplicationFolder folder: null + + property string folderName: folder ? folder.name : "" + property var folderModel: folder ? folder.applications : [] + + // don't set anchors.margins since we want everywhere to be draggable + required property real leftMargin + required property real rightMargin + required property bool twoColumn + + signal openConfigureRequested() + signal closeRequested() + + TapHandler { + onLongPressed: root.openConfigureRequested() + onTapped: root.closeRequested() + } + + header: MobileShell.BaseItem { + topPadding: Math.round(root.height * 0.2) + bottomPadding: PlasmaCore.Units.largeSpacing + leftPadding: root.leftMargin + rightPadding: root.rightMargin + implicitWidth: root.width + + background: Rectangle { + color: 'transparent' + TapHandler { + onLongPressed: root.openConfigureRequested() + onTapped: root.closeRequested() + } + } + contentItem: RowLayout { + spacing: PlasmaCore.Units.gridUnit + Kirigami.Icon { + Layout.alignment: Qt.AlignVCenter + Layout.preferredHeight: PlasmaCore.Units.iconSizes.small + Layout.preferredWidth: PlasmaCore.Units.iconSizes.small + isMask: true + color: 'white' + source: 'arrow-left' + + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 1 + radius: 4 + samples: 6 + color: Qt.rgba(0, 0, 0, 0.5) + } + } + QQC2.Label { + Layout.fillWidth: true + text: root.folderName + color: "white" + style: Text.Normal + styleColor: "transparent" + horizontalAlignment: Text.AlignLeft + + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 2 + + font.weight: Font.Bold + font.pointSize: 18 + layer.enabled: true + layer.effect: DropShadow { + verticalOffset: 1 + radius: 4 + samples: 6 + color: Qt.rgba(0, 0, 0, 0.5) + } + } + } + } + + model: DelegateModel { + id: visualModel + model: root.folderModel + + delegate: DropArea { + id: delegateRoot + property var application: model.application + + property int modelIndex + property int visualIndex: DelegateModel.itemsIndex + + width: root.cellWidth + height: root.cellHeight + + onEntered: (drag) => { + let from = (drag.source as MobileShell.BaseItem).visualIndex; + let to = appDelegate.visualIndex; + visualModel.items.move(from, to); + root.folder.moveEntry(from, to); + } + + //onDropped: (drag) => { + //let from = modelIndex; + //let to = (drag.source as MobileShell.BaseItem).visualIndex + //Halcyon.PinnedModel.moveEntry(from, to); + //} + + FavoritesAppDelegate { + id: appDelegate + visualIndex: delegateRoot.visualIndex + + isFolder: false + application: modelData + + readonly property bool isLeftColumn: !root.twoColumn || ((visualIndex % 2) === 0) + readonly property bool isRightColumn: !root.twoColumn || ((visualIndex % 2) !== 0) + leftPadding: isLeftColumn ? root.leftMargin : 0 + rightPadding: isRightColumn ? root.rightMargin : 0 + + implicitWidth: root.cellWidth + implicitHeight: visible ? root.cellHeight : 0 + + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + states: [ + State { + when: appDelegate.drag.active + ParentChange { + target: appDelegate + parent: root + } + + AnchorChanges { + target: appDelegate + anchors.horizontalCenter: undefined + anchors.verticalCenter: undefined + } + } + ] + } + } + } + + // animations + displaced: Transition { + NumberAnimation { + properties: "x,y" + easing.type: Easing.OutQuad + } + } +} diff --git a/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml b/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml index 10836271..812b8532 100644 --- a/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml +++ b/containments/homescreens/halcyon/package/contents/ui/HomeScreen.qml @@ -25,11 +25,9 @@ Item { function triggerHomescreen() { swipeView.setCurrentIndex(0); - favouritesList.contentY = favouritesList.originY; } function openConfigure() { - console.log('triggered') plasmoid.action("configure").trigger(); plasmoid.editMode = false; } @@ -53,12 +51,11 @@ Item { TapHandler { onLongPressed: root.openConfigure() } - - FavoritesGrid { + + FavoritesView { anchors.fill: parent searchWidget: root.searchWidget interactive: root.interactive - onOpenConfigureRequested: root.openConfigure() } }