feat(qml):实现拖拽合并或换位的UI部分功能,添加批量编辑UI

This commit is contained in:
qiqi49 2024-01-08 17:25:27 +08:00 committed by hewenfei
parent 83b4d14306
commit 713ba3d3e0
10 changed files with 519 additions and 200 deletions

View File

@ -1,5 +1,7 @@
import QtQuick 2.0
import org.ukui.menu.core 1.0
import org.ukui.quick.platform 1.0 as Platform
import org.ukui.quick.items 1.0 as UkuiItems
Item {
id: root
@ -66,10 +68,10 @@ Item {
cache: false
}
StyleText {
UkuiItems.StyleText {
id: iconText
text: root.appName
elide: Text.ElideRight
paletteRole: root.textHighLight ? Palette.HighlightedText : Palette.Text
paletteRole: root.textHighLight ? Platform.Theme.HighlightedText : Platform.Theme.Text
}
}

View File

@ -28,7 +28,8 @@ Item {
property bool textCenterIn: false
property bool editStatus: false
property string textEdited: title
property bool isFullScreenFolder: false
property bool isFullScreen: false
property bool isFolder: false
property real textInputSize: 14
Component {
@ -39,9 +40,13 @@ Item {
horizontalAlignment: contain.textCenterIn ? Text.AlignHCenter : Text.AlignLeft
elide: Text.ElideRight
text: contain.textEdited
paletteRole: isFullScreenFolder ? Platform.Theme.HighlightedText : Platform.Theme.Text
font.bold: !isFullScreenFolder
font.pointSize: isFullScreenFolder ? textUltra : systemFontSize
paletteRole: isFullScreen ? Platform.Theme.HighlightedText : Platform.Theme.Text
font.bold: !isFolder
// AppControls2 StyleText
property real textUltra: 2*systemFontSize
property real systemFontSize: 10
font.pointSize: isFolder ? textUltra : systemFontSize
MouseArea {
id: textArea
@ -78,7 +83,7 @@ Item {
text: contain.textEdited
selectByMouse: true
maximumLength: 14
font.bold: !isFullScreenFolder
font.bold: !isFolder
font.pointSize: contain.textInputSize
onEditingFinished: {
@ -87,7 +92,7 @@ Item {
contain.editStatus = false;
}
property int textColor: isFullScreenFolder ? Palette.HighlightedText : Palette.Text
property int textColor: isFullScreen ? Palette.HighlightedText : Palette.Text
function updateTextInputColor() {
color = themePalette.paletteColor(textColor);
selectionColor = themePalette.paletteColor(Palette.Highlight);

View File

@ -35,7 +35,8 @@ Loader {
property int viewMaxRow: 0
property bool isFolderOpened: false
property int margins: 20
property bool isFullScreen: true
property int margins: isFullScreen? 20 : 0
property int animationDuration: 300
property bool mouseEnable: false
signal turnPageFinished()
@ -72,17 +73,17 @@ Loader {
name: "folderOpened"
PropertyChanges {
target: folderIconBase
width: 720
height: viewMaxRow * 170 + margins * 2
width: isFullScreen ? 720 : 348
height: isFullScreen ? (viewMaxRow * 170 + margins * 2) : (viewMaxRow * 85 + margins * 2)
radius: Platform.Theme.maxRadius
gridViewMargin: margins
x: (parent.width - width) / 2
y: (parent.height - height) / 2
x: isFullScreen ? (parent.width - width) / 2 : 56
y: isFullScreen ? (parent.height - height) / 2 : 104
//
folderIconSize: 96
folderIconSize:isFullScreen ? 96 : 48
iconSpacing: 8
imageX: 37
imageY: 17
imageX: isFullScreen ? 37 : 13
imageY: isFullScreen ? 17 : 8
}
PropertyChanges { target: folderNameText; opacity: 1 }
},
@ -275,7 +276,7 @@ Loader {
}
onClicked: {
if (mouse.button === Qt.RightButton) {
menuManager.showMenu(id, MenuInfo.FullScreenFolder);
menuManager.showMenu(id, MenuInfo.Folder);
return;
}
if (mouse.button === Qt.LeftButton) {
@ -299,7 +300,8 @@ Loader {
height: 47; width: folderIconBase.width
textEdited: folderName
textCenterIn: true
isFullScreenFolder: true
isFolder: true
isFullScreen: isFullScreen
}
}
}

View File

@ -45,7 +45,7 @@ UkuiItems.StyleBackground {
Component.onDestruction: folderLoader.turnPageFinished.disconnect(contentShowFinished)
}
FullScreenFolder {
Folder {
id: folderLoader
anchors.fill: parent
Component.onCompleted: fullScreenContent.openFolderSignal.connect(initFolder)

View File

@ -12,3 +12,4 @@ PluginSelectButton 1.0 PluginSelectButton.qml
FullScreenHeader 1.0 FullScreenHeader.qml
FullScreenContent 1.0 FullScreenContent.qml
FullScreenFooter 1.0 FullScreenFooter.qml
Folder 1.0 Folder.qml

View File

@ -24,17 +24,20 @@ import org.ukui.menu.extension 1.0
import AppControls2 1.0 as AppControls2
import org.ukui.quick.platform 1.0 as Platform
import org.ukui.quick.items 1.0 as UkuiItems
import AppUI 1.0 as AppUI
UkuiMenuExtension {
Component.onCompleted: {
visualModel.model = extensionData.favoriteAppsModel
}
MouseArea {
id: viewMouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
folderLoader.isFolderOpened = false;
favoriteView.visible = true;
favoriteView.isContentShow = true;
}
UkuiItems.StyleBackground {
anchors.top: parent.top
width: parent.width; height: 1
@ -45,187 +48,26 @@ UkuiMenuExtension {
z: 1
}
GridView {
AppUI.Folder {
id: folderLoader
anchors.fill: parent
isFullScreen: false
Component.onCompleted: favoriteView.openFolderSignal.connect(initFolder)
Component.onDestruction: favoriteView.openFolderSignal.disconnect(initFolder)
}
FavoriteGridView {
id: favoriteView
anchors.fill: parent
anchors.leftMargin: 16
anchors.topMargin: 12
anchors.bottomMargin: 6
cellWidth: itemHeight + spacing; cellHeight: cellWidth
property int exchangedStartIndex: 0
property int spacing: 4
property int itemHeight: 104
property int column: Math.floor(width / cellWidth)
//
focus: true
onActiveFocusChanged: currentIndex = 0
onCountChanged: currentIndex = 0
Keys.onRightPressed: {
if(currentIndex === count - 1) {
currentIndex = 0;
return;
}
currentIndex++;
Component.onCompleted: {
favoriteView.viewModel.model = extensionData.favoriteAppsModel
folderLoader.turnPageFinished.connect(contentShowFinished)
}
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: DropArea {
id: delegateRoot
property int visualIndex: DelegateModel.itemsIndex
width: favoriteView.cellWidth; height: favoriteView.cellHeight
onEntered: {
visualModel.items.move(drag.source.visualIndex, icon.visualIndex)
}
Binding { target: icon; property: "visualIndex"; value: visualIndex }
focus: true
Keys.onReturnPressed: {
var data = {"id": model.id};
send(data);
}
states: State {
when: delegateRoot.activeFocus
PropertyChanges {
target: icon
alpha: 0.6
}
}
UkuiItems.StyleBackground {
id: icon
height: favoriteView.itemHeight; width: height
property bool hold: false
property int visualIndex: 0
x: 0; y: 0
radius: Platform.Theme.maxRadius
useStyleTransparency: false
scale: icon.hold ? 1.1 :1.0
alpha: control.containsPress ? 0.75 : control.containsMouse ? 0.6 : 0.40
Behavior on scale {
NumberAnimation { duration: 300; easing.type: Easing.InOutCubic }
}
AppControls2.IconLabel {
height: icon.height
width: icon.width - 14
anchors.centerIn: parent
appName: model.name
appIcon: model.icon
display: Display.TextUnderIcon
scale: (control.containsPress && !icon.hold) ? 1.1 : 1.0
ToolTip.visible: textTruncated && control.containsMouse
ToolTip.text: model.name
ToolTip.delay: 500
Behavior on scale {
NumberAnimation { duration: 300; easing.type: Easing.InOutCubic }
}
}
MouseArea {
id: control
anchors.fill: parent
hoverEnabled: true
pressAndHoldInterval: 300
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) {
visualModel.model.openMenu(index)
} else if (mouse.button === Qt.LeftButton) {
var data = {"id": model.id};
send(data);
}
}
onPressAndHold: {
if (mouse.button === Qt.LeftButton) {
drag.target = icon;
icon.hold = true;
favoriteView.exchangedStartIndex = icon.visualIndex;
}
}
onReleased: {
icon.hold = false;
drag.target = null;
}
}
onHoldChanged: {
if (hold) {
favoriteView.interactive = false;
} else {
favoriteView.interactive = true;
visualModel.model.exchangedAppsOrder(favoriteView.exchangedStartIndex, icon.visualIndex);
}
}
Drag.active: control.drag.active
Drag.source: icon
Drag.hotSpot.x: icon.width / 2
Drag.hotSpot.y: icon.height / 2
Drag.onActiveChanged: {
if (Drag.active) {
icon.parent = favoriteView;
} else {
iconResetAnimation.start();
}
}
ParallelAnimation {
id: iconResetAnimation
NumberAnimation {
target: icon
property: "x"
to: delegateRoot.x
easing.type: Easing.OutQuad
duration: 300
}
NumberAnimation {
target: icon
property: "y"
to: delegateRoot.y - favoriteView.contentY
easing.type: Easing.OutQuad
duration: 300
}
onFinished: {
icon.parent = delegateRoot;
icon.x = 0; icon.y = 0;
}
}
}
}
}
}
Component.onDestruction: folderLoader.turnPageFinished.disconnect(contentShowFinished)
}
}
}

View File

@ -0,0 +1,451 @@
/*
* 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;
}
}
}
}
}
}

View File

@ -0,0 +1,10 @@
import QtQuick 2.12
import QtQml.Models 2.1
import QtQuick.Controls 2.5
import org.ukui.menu.core 1.0
import org.ukui.menu.extension 1.0
import AppControls2 1.0 as AppControls2
Item {
}

View File

@ -25,13 +25,15 @@
<file>AppControls2/LabelItem.qml</file>
<file>AppControls2/FolderItem.qml</file>
<file>extensions/FavoriteExtension.qml</file>
<file>extensions/FavoriteGridView.qml</file>
<file>extensions/FolderGridView.qml</file>
<file>AppControls2/RoundButton.qml</file>
<file>AppControls2/FolderIcon.qml</file>
<file>AppUI/SelectionPage.qml</file>
<file>AppUI/AppPageContent.qml</file>
<file>AppUI/PluginSelectButton.qml</file>
<file>AppUI/EditText.qml</file>
<file>AppUI/FullScreenFolder.qml</file>
<file>AppUI/Folder.qml</file>
<file>AppUI/AppPageSearch.qml</file>
</qresource>
</RCC>

View File

@ -122,6 +122,10 @@ void AppFavoritesModel::updateFavoritesApps(const DataEntity &app, const QModelI
addFavoriteApp(index);
} else if (app.favorite() == 0) {
if (FavoriteFolderHelper::instance()->containApp(app.id())) {
FavoriteFolderHelper::instance()->removeAppFromFolder(app.id());
}
QPersistentModelIndex index(sourceIndex);
removeFavoriteApp(index);
}