Commit 564c3f9e authored by Devin Lin's avatar Devin Lin 🎨
Browse files

homescreens/halcyon: Add initial folders implementation

parent 1629733d
Pipeline #197273 passed with stage
in 1 minute and 10 seconds
......@@ -54,6 +54,16 @@ void ApplicationFolder::setName(QString &name)
Q_EMIT saveRequested();
}
QList<Application *> ApplicationFolder::appPreviews()
{
QList<Application *> 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<Application *> ApplicationFolder::applications()
{
return m_applications;
......@@ -66,6 +76,28 @@ void ApplicationFolder::setApplications(QList<Application *> 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()) {
......
......@@ -22,6 +22,7 @@ class ApplicationFolder : public QObject
{
Q_OBJECT
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QList<Application *> appPreviews READ appPreviews NOTIFY applicationsChanged)
Q_PROPERTY(QList<Application *> applications READ applications NOTIFY applicationsChanged)
public:
......@@ -33,9 +34,12 @@ public:
QString name() const;
void setName(QString &name);
QList<Application *> appPreviews();
QList<Application *> applications();
void setApplications(QList<Application *> applications);
Q_INVOKABLE void moveEntry(int fromRow, int toRow);
Q_INVOKABLE void addApp(const QString &storageId, int row);
Q_INVOKABLE void removeApp(int row);
......
......@@ -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)
}
}
}
}
}
}
}
}
......
......@@ -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 openConfigureRequested()
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
......
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// 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
}
}
}
}
// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// 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()