ukui-menu/qml/extensions/FavoriteGridView.qml

452 lines
17 KiB
QML

/*
* Copyright (C) 2023, KylinSoft Co., Ltd.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
import QtQuick 2.15
import QtQml.Models 2.1
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.15
import org.ukui.menu.core 1.0
import org.ukui.menu.extension 1.0
import org.ukui.quick.platform 1.0 as Platform
import org.ukui.quick.items 1.0 as UkuiItems
import AppControls2 1.0 as AppControls2
GridView {
id: favoriteView
cellWidth: itemHeight + spacing; cellHeight: cellWidth
signal openFolderSignal(string folderId, string folderName, int x, int y)
signal contentShowFinished()
property bool isContentShow: true
property int exchangedStartIndex: 0
property int spacing: 4
property int itemHeight: 84
property int column: Math.floor(width / cellWidth)
property alias viewModel: visualModel
property string mergeToAppId: ""
property bool isMergeToFolder: false
property bool dragTypeIsMerge: false
state: isContentShow ? "contentShow" : "contentHidden"
states: [
State {
name: "contentHidden"
PropertyChanges { target: favoriteView; opacity: 0; scale: 0.95 }
},
State {
name: "contentShow"
PropertyChanges { target: favoriteView; opacity: 1; scale: 1 }
}
]
transitions: [
Transition {
to:"contentHidden"
SequentialAnimation {
PropertyAnimation { properties: "opacity, scale"; duration: 300; easing.type: Easing.InOutCubic }
ScriptAction { script: favoriteView.visible = false }
}
},
Transition {
to: "contentShow"
PropertyAnimation { properties: "opacity, scale"; duration: 300; easing.type: Easing.InOutCubic }
}
]
// 按键导航处理(左右键可以循环)
focus: true
onActiveFocusChanged: currentIndex = 0
onCountChanged: currentIndex = 0
Keys.onRightPressed: {
if(currentIndex === count - 1) {
currentIndex = 0;
return;
}
currentIndex++;
}
Keys.onLeftPressed: {
if(currentIndex === 0) {
currentIndex = count - 1;
return;
}
currentIndex--;
}
Keys.onDownPressed: {
if(currentIndex > count - 1 - column) {
return;
}
currentIndex = currentIndex + column;
}
Keys.onUpPressed: {
if(currentIndex < column) {
return;
}
currentIndex = currentIndex - column;
}
ScrollBar.vertical: AppControls2.ScrollBar {
id: scrollBar
visible: viewMouseArea.containsMouse
width: 14; height: favoriteView.height
}
displaced: Transition {
NumberAnimation { properties: "x,y"; easing.type: Easing.OutQuad; duration: 200 }
}
model: DelegateModel {
id: visualModel
delegate: Item {
id: container
focus: true
width: favoriteView.cellWidth
height: favoriteView.cellHeight
property int visualIndex: DelegateModel.itemsIndex
Binding { target: iconItem; property: "visualIndex"; value: visualIndex }
states: State {
when: activeFocus
PropertyChanges {
target: iconItem
alpha: 0.6
}
}
Keys.onReturnPressed: {
var data = {"id": model.id};
send(data);
}
Timer {
id: delegateDropTimer
property int timeOutCount: 0
interval: 300
repeat: true
onTriggered: {
++timeOutCount;
if (timeOutCount == 1) {
mergeToAppId = model.id;
isMergeToFolder = (model.type === DataType.Folder);
dragTypeIsMerge = true;
}
}
onRunningChanged:timeOutCount = 0
}
DropArea {
id: delegateDropArea
width: 48; height: 48
anchors.top: parent.top
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
property string enterSourceId: ""
onEntered: {
if (drag.source.isFolder) {
return;
}
enterSourceId = drag.source.sourceId;
delegateDropTimer.running = true;
}
onExited: {
if (delegateDropTimer.timeOutCount < 1) {
dragTypeIsMerge = false;
visualModel.items.move(drag.source.visualIndex, iconItem.visualIndex);
}
delegateDropTimer.running = false;
enterSourceId = "";
}
}
UkuiItems.StyleBackground {
id: iconItem
height: favoriteView.itemHeight; width: height
property bool hold: false
property int visualIndex: 0
property string sourceId: ""
property bool isFolder: model.type === DataType.Folder
x: 0; y: 0
radius: 8
useStyleTransparency: false
paletteRole: Platform.Theme.Text
alpha: hold ? 0 : control.containsPress ? 0.15 : control.containsMouse ? 0.08 : 0
Behavior on scale {
NumberAnimation { duration: 300; easing.type: Easing.InOutCubic }
}
UkuiItems.StyleBackground {
width: 52
height: width
radius: 14
anchors.top: parent.top
anchors.margins: 6
anchors.horizontalCenter: parent.horizontalCenter
paletteRole: Platform.Theme.Text
useStyleTransparency: false
alpha: (delegateDropArea.enterSourceId !== model.id)
&& delegateDropArea.containsDrag && dragTypeIsMerge ? 0.15 : 0
z: -1
}
Loader {
id: itemLoader
anchors.centerIn: parent
property int index: model.index
property int type: model.type
property string id: model.id
property string name: model.name
property string icon: model.icon
sourceComponent: {
if (type === DataType.Normal) {
return appItemComponent;
}
if (type === DataType.Folder) {
return folderItemComponent;
}
}
}
Component {
id: appItemComponent
Item {
height: iconItem.height
width: iconItem.width
ToolTip.visible: iconText.truncated && control.containsMouse
ToolTip.text: model.name
ToolTip.delay: 500
function itemClicked(mouseButton) {
if (mouseButton === Qt.RightButton) {
visualModel.model.openMenu(index)
} else {
var data = {"id": model.id};
send(data);
}
}
ColumnLayout {
width: parent.width
anchors.top: parent.top
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
spacing: 6
Image {
id: iconImage
Layout.preferredHeight: 48
Layout.preferredWidth: 48
sourceSize.height: height
sourceSize.width: width
source: icon
cache: false
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
}
UkuiItems.StyleText {
id: iconText
Layout.fillWidth: true
text: name
elide: Text.ElideRight
paletteRole: Platform.Theme.Text
horizontalAlignment: Text.AlignHCenter
}
}
Loader {
id: tag
visible: mainWindow.editMode
anchors.top: parent.top
anchors.right: parent.right
anchors.rightMargin: 10
Component {
id: editImage
UkuiItems.Button {
width: 28
height: 28
icon.width: 16
icon.height: 16
background.paletteRole: Platform.Theme.Light
background.alpha: 1
activeFocusOnTab: false
onClicked: {
visualModel.model.removeAppFromFavorites(id);
}
background.radius: width / 2
icon.source: "ukui-cancel-star-symbolic"
}
}
sourceComponent: mainWindow.editMode ? editImage : null
}
}
}
Component {
id: folderItemComponent
UkuiItems.StyleBackground {
height: iconItem.height
width: iconItem.width - 14
useStyleTransparency: false
paletteRole: Platform.Theme.Light
radius: 6
alpha: parent.containsPress ? 0.25 : parent.containsMouse ? 0.15 : 0.00
function itemClicked(mouseButton) {
if (mouseButton === Qt.RightButton) {
visualModel.model.openMenu(index);
} else {
var x = mapToGlobal(0,0).x;
var y = mapToGlobal(0,0).y
openFolderSignal(id, name, x, y);
// 执行隐藏动画,并且当前图标消失且鼠标区域不可用
favoriteView.isContentShow = false;
opacity = 0;
control.enabled = false;
control.hoverEnabled = false;
}
}
Item {
id: folderItem
property bool isSelect: false
anchors.horizontalCenter: parent.horizontalCenter
height: 40; width: 40
anchors.top: parent.top
anchors.topMargin: 12
UkuiItems.StyleBackground {
anchors.fill: parent
paletteRole: Palette.Text
useStyleTransparency: false
alpha: 0.25
radius: 24
visible: folderItem.isSelect
}
AppControls2.FolderIcon {
id: folderIcon
anchors.fill: parent
rows: 2; columns: 2
spacing: 2; padding: 2
icons: icon
radius: 4; alpha: folderItem.isSelect ? 0 : 0.25
anchors.centerIn: parent
}
}
UkuiItems.StyleText {
id: folderText
anchors.bottom: parent.bottom
anchors.bottomMargin: 14
width: parent.width
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
elide: Text.ElideRight
text: name
}
}
}
MouseArea {
id: control
anchors.fill: parent
hoverEnabled: true
pressAndHoldInterval: 300
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
itemLoader.item.itemClicked(mouse.button);
}
onPressAndHold: {
if (mouse.button === Qt.LeftButton) {
drag.target = iconItem;
iconItem.hold = true;
exchangedStartIndex = iconItem.visualIndex;
iconItem.sourceId = model.id;
}
}
onReleased: {
iconItem.hold = false;
drag.target = null;
}
}
// folderFunction
function resetOpacity() {
itemLoader.item.opacity = 1;
control.enabled = true;
control.hoverEnabled = true;
}
Component.onCompleted: favoriteView.contentShowFinished.connect(resetOpacity)
Component.onDestruction: favoriteView.contentShowFinished.disconnect(resetOpacity)
onHoldChanged: {
if (hold) {
favoriteView.interactive = false;
} else {
favoriteView.interactive = true;
if (dragTypeIsMerge && (model.id !== mergeToAppId) && (model.type === DataType.Normal)) {
if (isMergeToFolder) {
visualModel.model.addAppToFolder(model.id, mergeToAppId);
} else {
visualModel.model.addAppsToNewFolder(model.id, mergeToAppId);
}
} else if (exchangedStartIndex != iconItem.visualIndex) {
visualModel.model.exchangedAppsOrder(exchangedStartIndex, iconItem.visualIndex);
}
}
}
Drag.active: control.drag.active
Drag.source: iconItem
Drag.hotSpot.x: iconItem.width / 2
Drag.hotSpot.y: iconItem.height / 2
Drag.onActiveChanged: {
if (Drag.active) {
iconItem.parent = favoriteView;
} else {
iconResetAnimation.start();
}
}
ParallelAnimation {
id: iconResetAnimation
NumberAnimation {
target: iconItem
property: "x"
to: container.x
easing.type: Easing.OutQuad
duration: 300
}
NumberAnimation {
target: iconItem
property: "y"
to: container.y - favoriteView.contentY
easing.type: Easing.OutQuad
duration: 300
}
onFinished: {
iconItem.parent = container;
iconItem.x = 0; iconItem.y = 0;
}
}
}
}
}
}