feat(qml):按照设计稿优化收藏界面显示,添加空白区域右键菜单

This commit is contained in:
qiqi49 2024-01-30 09:50:09 +08:00 committed by hewenfei
parent 379b07fc7a
commit fbc3f02c10
7 changed files with 361 additions and 297 deletions

View File

@ -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);
}
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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
}
}
}

View File

@ -24,6 +24,7 @@
<file>AppControls2/LabelItem.qml</file>
<file>AppControls2/FolderItem.qml</file>
<file>extensions/FavoriteExtension.qml</file>
<file>extensions/FavoriteDelegate.qml</file>
<file>extensions/FavoriteGridView.qml</file>
<file>extensions/FolderGridView.qml</file>
<file>AppControls2/RoundButton.qml</file>

View File

@ -103,6 +103,7 @@ void AppFavoritesModel::getFoldersId()
QVector<int> 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);
}

View File

@ -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();
}

View File

@ -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