shift-shell/containments/homescreens/halcyon/package/contents/ui/FavoritesGrid.qml
2024-07-26 23:47:44 -04:00

298 lines
10 KiB
QML

// SPDX-FileCopyrightText: 2022 Devin Lin <devin@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQml.Models
import org.kde.plasma.components 3.0 as PC3
import org.kde.draganddrop as DragDrop
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mobileshell as MobileShell
import org.kde.private.mobile.homescreen.halcyon as Halcyon
MobileShell.GridView {
id: root
required property var searchWidget
// don't set anchors.margins since we want everywhere to be draggable
required property bool twoColumn
signal openConfigureRequested()
signal requestOpenFolder(Halcyon.ApplicationFolder folder)
// search widget open gesture
property bool openingSearchWidget: false
property bool canOpenSearchWidget: false
property real oldVerticalOvershoot: verticalOvershoot
onVerticalOvershootChanged: {
if (dragging && canOpenSearchWidget && verticalOvershoot < 0) {
if (!openingSearchWidget) {
if (oldVerticalOvershoot === 0) {
openingSearchWidget = true;
root.searchWidget.startGesture();
}
} else {
let offset = -(verticalOvershoot - oldVerticalOvershoot);
root.searchWidget.updateGestureOffset(-offset);
}
}
oldVerticalOvershoot = verticalOvershoot;
}
onDraggingChanged: {
if (dragging) {
canOpenSearchWidget = root.contentY <= 0;
} else if (!dragging && openingSearchWidget) {
openingSearchWidget = false;
root.searchWidget.endGesture();
}
}
// open wallpaper menu when held on click
TapHandler {
onLongPressed: root.openConfigureRequested()
}
header: MobileShell.BaseItem {
topPadding: Math.round(root.height * 0.2)
bottomPadding: Kirigami.Units.gridUnit
// leftPadding: root.leftMargin
// rightPadding: root.rightMargin
implicitWidth: root.width
background: Rectangle {
color: 'transparent'
TapHandler { onLongPressed: root.openConfigureRequested() } // open wallpaper menu when held on click
}
contentItem: Clock {}
}
Keys.onReturnPressed: currentItem.appDelegate.launch()
model: DelegateModel {
id: visualModel
model: Halcyon.PinnedModel
delegate: Item {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
property alias appDelegate: appDelegate
width: root.cellWidth
height: root.cellHeight
function moveDragToCurrentPos(from, to) {
if (from !== to) {
visualModel.items.move(from, to);
Halcyon.PinnedModel.moveEntry(from, to);
}
}
function topDragEnter(drag) {
if (transitionAnim.running || appDelegate.drag.active) return; // don't do anything when reordering
let fromIndex = drag.source.visualIndex;
let delegateVisualIndex = appDelegate.visualIndex;
let reorderIndex = -1;
if (fromIndex < delegateVisualIndex) { // dragged item from above
// move to spot above
reorderIndex = delegateVisualIndex - (root.twoColumn ? 2 : 1);
} else { // dragged item from below
// move to current spot
reorderIndex = delegateVisualIndex;
}
if (reorderIndex >= 0 && reorderIndex < root.count) {
delegateRoot.moveDragToCurrentPos(fromIndex, reorderIndex)
}
}
function bottomDragEnter(drag) {
if (transitionAnim.running || appDelegate.drag.active) return; // don't do anything when reordering
let fromIndex = drag.source.visualIndex;
let delegateVisualIndex = appDelegate.visualIndex;
let reorderIndex = -1;
if (fromIndex < delegateVisualIndex) { // dragged item from above
// move to current spot
reorderIndex = delegateVisualIndex;
} else { // dragged item from below
// move to spot below
reorderIndex = delegateVisualIndex + (root.twoColumn ? 2 : 1);
}
if (reorderIndex >= 0 && reorderIndex < root.count) {
delegateRoot.moveDragToCurrentPos(fromIndex, reorderIndex);
}
}
// top drop area
DropArea {
id: topDropArea
anchors.top: parent.top
anchors.left: leftDropArea.right
anchors.right: rightDropArea.left
height: delegateRoot.height * 0.2
onEntered: (drag) => delegateRoot.topDragEnter(drag)
}
// bottom drop area
DropArea {
id: bottomDropArea
anchors.bottom: parent.bottom
anchors.left: leftDropArea.right
anchors.right: rightDropArea.left
height: delegateRoot.height * 0.2
onEntered: (drag) => delegateRoot.bottomDragEnter(drag)
}
// left drop area
DropArea {
id: leftDropArea
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.left: parent.left
width: root.twoColumn ? Math.max(appDelegate.leftPadding, delegateRoot.width * 0.1) : 0
onEntered: (drag) => delegateRoot.topDragEnter(drag)
}
// right drop area
DropArea {
id: rightDropArea
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.right: parent.right
width: root.twoColumn ? Math.max(appDelegate.rightPadding, delegateRoot.width * 0.1) : 0
onEntered: (drag) => delegateRoot.bottomDragEnter(drag)
}
// folder drop area
DropArea {
anchors.top: topDropArea.bottom
anchors.bottom: bottomDropArea.top
anchors.left: leftDropArea.right
anchors.right: rightDropArea.left
onEntered: (drag) => {
if (transitionAnim.running || appDelegate.drag.active || drag.source.isFolder) return; // don't do anything when reordering
folderAnim.to = 1;
folderAnim.restart();
}
onExited: () => {
folderAnim.to = 0;
folderAnim.restart();
}
onDropped: (drop) => {
if (transitionAnim.running || appDelegate.drag.active || drag.source.isFolder) return; // don't do anything when reordering
if (appDelegate.isFolder) {
Halcyon.PinnedModel.addAppToFolder(drop.source.visualIndex, appDelegate.visualIndex);
} else {
Halcyon.PinnedModel.createFolderFromApps(drop.source.visualIndex, appDelegate.visualIndex);
}
folderAnim.to = 0;
folderAnim.restart();
}
NumberAnimation {
id: folderAnim
target: appDelegate
properties: "dragFolderAnimationProgress"
duration: 100
}
}
// actual visual delegate
FavoritesAppDelegate {
id: appDelegate
visualIndex: delegateRoot.visualIndex
isFolder: model.isFolder
folder: model.folder
application: model.application
onFolderOpenRequested: root.requestOpenFolder(model.folder)
menuActions: [
Kirigami.Action {
icon.name: "emblem-favorite"
text: i18n("Remove from favourites")
onTriggered: Halcyon.PinnedModel.removeEntry(model.index)
}
]
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 {
id: transitionAnim
properties: "x,y"
easing.type: Easing.OutQuad
}
}
ColumnLayout {
id: placeholder
spacing: Kirigami.Units.gridUnit
visible: root.count == 0
opacity: 0.9
anchors.fill: parent
anchors.topMargin: Math.round(swipeView.height * 0.2) - (root.contentY - root.originY)
anchors.leftMargin: root.leftMargin
anchors.rightMargin: root.rightMargin
layer.enabled: true
layer.effect: MobileShell.TextDropShadow {}
Kirigami.Icon {
id: icon
Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter
implicitWidth: Kirigami.Units.iconSizes.large
implicitHeight: width
source: "arrow-left"
color: "white"
}
Kirigami.Heading {
Layout.fillWidth: true
Layout.maximumWidth: placeholder.width * 0.75
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
color: "white"
level: 3
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
text: i18n("Add applications to your favourites so they show up here.")
}
TapHandler {
onLongPressed: root.openConfigureRequested()
}
}
}