From fbc3f02c100709ad9732cf2e1d4f21e66f4b62fe Mon Sep 17 00:00:00 2001 From: qiqi49 Date: Tue, 30 Jan 2024 09:50:09 +0800 Subject: [PATCH] =?UTF-8?q?feat(qml):=E6=8C=89=E7=85=A7=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E7=A8=BF=E4=BC=98=E5=8C=96=E6=94=B6=E8=97=8F=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=A9=BA=E7=99=BD?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E5=8F=B3=E9=94=AE=E8=8F=9C=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- qml/extensions/FavoriteDelegate.qml | 234 +++++++++++++ qml/extensions/FavoriteExtension.qml | 69 +++- qml/extensions/FavoriteGridView.qml | 331 +++--------------- qml/qml.qrc | 1 + src/extension/favorite/app-favorite-model.cpp | 1 + .../favorite/favorite-folder-helper.cpp | 12 +- src/extension/favorite/favorites-config.cpp | 10 +- 7 files changed, 361 insertions(+), 297 deletions(-) create mode 100644 qml/extensions/FavoriteDelegate.qml diff --git a/qml/extensions/FavoriteDelegate.qml b/qml/extensions/FavoriteDelegate.qml new file mode 100644 index 0000000..120c2b0 --- /dev/null +++ b/qml/extensions/FavoriteDelegate.qml @@ -0,0 +1,234 @@ +import QtQuick 2.15 +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 + +UkuiItems.StyleBackground { + id: iconItem + property bool hold: false + property alias draggedIcon: itemLoader + radius: 8 + useStyleTransparency: false + paletteRole: Platform.Theme.Text + alpha: hold ? 0 : control.containsPress ? 0.15 : control.containsMouse ? 0.08 : 0 + + // 合并提升框 52*52 + 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 + } + + ColumnLayout { + id: itemLayout + width: parent.width + anchors.top: parent.top + anchors.topMargin: 8 + anchors.horizontalCenter: parent.horizontalCenter + spacing: 6 + + ToolTip.visible: iconText.truncated && control.containsMouse + ToolTip.text: model.name + ToolTip.delay: 500 + + Item { + id: loaderBase + Layout.preferredHeight: 48 + Layout.preferredWidth: 48 + Layout.alignment: Qt.AlignHCenter + + Loader { + id: itemLoader + width: loaderBase.width + height: loaderBase.height + x: 0; y: 0 + Drag.active: control.drag.active + Drag.source: itemLoader + Drag.hotSpot.x: width / 2 + Drag.hotSpot.y: height / 2 + + Drag.onActiveChanged: { + if (Drag.active) { + itemLoader.parent = favoriteView; + } else { + if (dragTypeIsMerge && (model.id !== mergeToAppId) && (model.type === DataType.Normal)) { + iconItem.hold = false; + + } else { + iconResetAnimation.start(); + } + } + } + + ParallelAnimation { + id: iconResetAnimation + NumberAnimation { + target: itemLoader + property: "x" + to: container.x + loaderBase.x + easing.type: Easing.OutQuad + duration: 300 + } + NumberAnimation { + target: itemLoader + property: "y" + to: (container.y + 8) - favoriteView.contentY + easing.type: Easing.OutQuad + duration: 300 + } + + onFinished: { + iconItem.hold = false; + itemLoader.parent = loaderBase; + itemLoader.x = 0; itemLoader.y = 0; + } + } + + property int visualIndex: 0 + property string sourceId: "" + property bool isFolder: model.type === DataType.Folder + property string icon: model.icon + property bool isFavorite: true + sourceComponent: { + if (type === DataType.Normal) { + return appIconComponent; + } + if (type === DataType.Folder) { + return folderIconComponent; + } + } + } + } + + + UkuiItems.StyleText { + id: iconText + Layout.fillWidth: true + + text: name + elide: Text.ElideRight + paletteRole: Platform.Theme.Text + horizontalAlignment: Text.AlignHCenter + opacity: !itemLoader.Drag.active + + Behavior on opacity { + NumberAnimation { duration: 150} + } + } + } + + Component { + id: appIconComponent + Image { + id: iconImage + sourceSize.height: height + sourceSize.width: width + source: icon + cache: false + + function itemClicked(mouseButton) { + if (mouseButton === Qt.RightButton) { + visualModel.model.openMenu(index) + } else { + var data = {"id": model.id}; + send(data); + } + } + } + } + + Component { + id: folderIconComponent + Item { + AppControls2.FolderIcon { + id: folderIcon + width: 40 + height: 40 + anchors.centerIn: parent + + rows: 2; columns: 2 + spacing: 2; padding: 2 + icons: icon + radius: 4; alpha: 0.25 + } + + 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; + } + } + } + } + + 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 = itemLoader; + iconItem.hold = true; + exchangedStartIndex = itemLoader.visualIndex; + itemLoader.sourceId = model.id; + } + } + onReleased: { + 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 = contentHeight > favoriteView.parent.height; + 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 !== itemLoader.visualIndex) { + visualModel.model.exchangedAppsOrder(exchangedStartIndex, itemLoader.visualIndex); + } + } + } +} + diff --git a/qml/extensions/FavoriteExtension.qml b/qml/extensions/FavoriteExtension.qml index c0acd96..fc6937a 100644 --- a/qml/extensions/FavoriteExtension.qml +++ b/qml/extensions/FavoriteExtension.qml @@ -31,13 +31,35 @@ UkuiMenuExtension { id: viewMouseArea anchors.fill: parent hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { - folderLoader.isFolderOpened = false; - favoriteView.visible = true; - favoriteView.isContentShow = true; - } + if (mouse.button === Qt.RightButton) { + menu.open(); + } else { + if (mainWindow.editMode) { + mainWindow.editMode = false; + } + folderLoader.isFolderOpened = false; + favoriteView.visible = true; + favoriteView.isContentShow = true; + } + + } + UkuiItems.Menu { + id: menu + content: [ + UkuiItems.MenuItem { + text: qsTr("Enable editing mode") + onClicked: mainWindow.editMode = true; + }, + UkuiItems.MenuItem { + text: qsTr("Remove all favorite apps") + onClicked: extensionData.favoriteAppsModel.clearFavorites(); + } + ] + } UkuiItems.StyleBackground { anchors.top: parent.top width: parent.width; height: 1 @@ -58,17 +80,38 @@ UkuiMenuExtension { Component.onDestruction: favoriteView.openFolderSignal.disconnect(initFolder) } - FavoriteGridView { - id: favoriteView + Item { anchors.fill: parent + anchors.bottomMargin: 8 anchors.leftMargin: 16 - anchors.topMargin: 12 - anchors.bottomMargin: 6 - Component.onCompleted: { - favoriteView.viewModel.model = extensionData.favoriteAppsModel - folderLoader.turnPageFinished.connect(contentShowFinished) + anchors.rightMargin: 16 + anchors.topMargin: 8 + + // 拖动到文件到空白区域 添加到收藏 + DropArea { + anchors.fill: parent + onEntered: { + if (drag.source.isFavorite) { + favoriteView.dragTypeIsMerge = false; + } else { + var id = drag.source.id; + extensionData.favoriteAppsModel.addAppToFavorites(id); + } + } } - Component.onDestruction: folderLoader.turnPageFinished.disconnect(contentShowFinished) - } + + FavoriteGridView { + id: favoriteView + width: parent.width + height: (contentHeight > parent.height) ? parent.height : contentHeight + interactive: contentHeight > parent.height + + Component.onCompleted: { + favoriteView.viewModel.model = extensionData.favoriteAppsModel + folderLoader.turnPageFinished.connect(contentShowFinished) + } + Component.onDestruction: folderLoader.turnPageFinished.disconnect(contentShowFinished) + } + } } } diff --git a/qml/extensions/FavoriteGridView.qml b/qml/extensions/FavoriteGridView.qml index 3bb85f6..c73cf11 100644 --- a/qml/extensions/FavoriteGridView.qml +++ b/qml/extensions/FavoriteGridView.qml @@ -28,19 +28,21 @@ import AppControls2 1.0 as AppControls2 GridView { id: favoriteView - cellWidth: itemHeight + spacing; cellHeight: cellWidth + cellWidth: width / column + cellHeight: cellWidth + 8 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 int column: 5 property alias viewModel: visualModel + property string mergeToAppId: "" property bool isMergeToFolder: false property bool dragTypeIsMerge: false + property int exchangedStartIndex: 0 state: isContentShow ? "contentShow" : "contentHidden" states: [ @@ -116,7 +118,7 @@ GridView { width: favoriteView.cellWidth height: favoriteView.cellHeight property int visualIndex: DelegateModel.itemsIndex - Binding { target: iconItem; property: "visualIndex"; value: visualIndex } + Binding { target: iconItem.draggedIcon; property: "visualIndex"; value: visualIndex } states: State { when: activeFocus PropertyChanges { @@ -129,31 +131,13 @@ GridView { 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 + anchors.fill: parent + anchors.margins: 14 property string enterSourceId: "" + // drag.source [iconItem.draggedIcon] onEntered: { if (drag.source.isFolder) { return; @@ -173,277 +157,60 @@ GridView { } } - 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 + Item { + anchors.fill: parent + anchors.margins: 2 - Behavior on scale { - NumberAnimation { duration: 300; easing.type: Easing.InOutCubic } - } + Timer { + id: delegateDropTimer + property int timeOutCount: 0 + interval: 300 + repeat: true - 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 + onTriggered: { + ++timeOutCount; + if (timeOutCount == 1) { + mergeToAppId = model.id; + isMergeToFolder = (model.type === DataType.Folder); + dragTypeIsMerge = true; } } + onRunningChanged: timeOutCount = 0 } - MouseArea { - id: control + FavoriteDelegate { + id: iconItem 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; - } - + property int visualIndex: container.DelegateModel.itemsIndex } - // 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); + //编辑模式标志 + Loader { + id: tag + 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); } - } else if (exchangedStartIndex != iconItem.visualIndex) { - visualModel.model.exchangedAppsOrder(exchangedStartIndex, iconItem.visualIndex); + background.radius: width / 2 + icon.source: "ukui-cancel-star-symbolic" } } - } - 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; - } + sourceComponent: mainWindow.editMode && (type === DataType.Normal) ? editImage : null } } } diff --git a/qml/qml.qrc b/qml/qml.qrc index 89e7a32..0d8ce87 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -24,6 +24,7 @@ AppControls2/LabelItem.qml AppControls2/FolderItem.qml extensions/FavoriteExtension.qml + extensions/FavoriteDelegate.qml extensions/FavoriteGridView.qml extensions/FolderGridView.qml AppControls2/RoundButton.qml diff --git a/src/extension/favorite/app-favorite-model.cpp b/src/extension/favorite/app-favorite-model.cpp index 02437fd..132f509 100644 --- a/src/extension/favorite/app-favorite-model.cpp +++ b/src/extension/favorite/app-favorite-model.cpp @@ -103,6 +103,7 @@ void AppFavoritesModel::getFoldersId() QVector foldersId; for (const auto &folder : FavoriteFolderHelper::instance()->folderData()) { foldersId.append(folder.getId()); + FavoritesConfig::instance().insertValue(FOLDER_ID_SCHEME + QString::number(folder.getId())); } m_folders.swap(foldersId); } diff --git a/src/extension/favorite/favorite-folder-helper.cpp b/src/extension/favorite/favorite-folder-helper.cpp index 4feff87..df09d4f 100644 --- a/src/extension/favorite/favorite-folder-helper.cpp +++ b/src/extension/favorite/favorite-folder-helper.cpp @@ -148,7 +148,17 @@ void FavoriteFolderHelper::addAppsToNewFolder(const QString &idFrom, const QStri folder.apps.append(idTo); insertFolder(folder); - Q_EMIT folderAdded(folder.id, FavoritesConfig::instance().getOrderById(APP_ID_SCHEME + idTo)); + // 确定folder位置 + int orderTo = FavoritesConfig::instance().getOrderById(APP_ID_SCHEME + idTo); + int orderFrom = FavoritesConfig::instance().getOrderById(APP_ID_SCHEME + idFrom); + int folderOrder; + if (orderFrom > orderTo) { + folderOrder = orderTo; + } else { + folderOrder = orderTo - 1; + } + + Q_EMIT folderAdded(folder.id, folderOrder); forceSync(); } diff --git a/src/extension/favorite/favorites-config.cpp b/src/extension/favorite/favorites-config.cpp index efcb5d0..0403a94 100644 --- a/src/extension/favorite/favorites-config.cpp +++ b/src/extension/favorite/favorites-config.cpp @@ -138,11 +138,19 @@ void FavoritesConfig::initConfig() m_favoritesList.clear(); QJsonArray array = jsonDocument.array(); + QJsonArray newArray; for (int i = 0; i < array.size(); i++) { - if (array.at(i).toObject().contains("id")) { + if (array.at(i).isString()) { m_favoritesList.append(array.at(i).toString()); + newArray.append(array.at(i)); } } + + file.open(QFile::WriteOnly); + jsonDocument.setArray(newArray); + file.write(jsonDocument.toJson()); + file.flush(); + file.close(); } } // UkuiMenu