feat(StatusChatListAndCategories): add drag and drop support for cate… (#349)

* feat(StatusChatListAndCategories): add drag and drop support for categories

This adds support for dragging and dropping chat list categories.
To persist reorder of chat categories, the new `onChatListCategoryReordered`
signal can be leveraged.

Drag and drop of categories is turned off by default and needs to
be turned on using `draggableCategories: true`.

Closes #227

* feat(Status.Core): introduce Utils namespace

This adds a new package for utility related things.
This commit is contained in:
Pascal Precht 2021-08-26 21:33:45 +02:00 committed by Michał Cieślak
parent c4aa67a751
commit cebfe60d50
9 changed files with 161 additions and 32 deletions

View File

@ -9,6 +9,7 @@ These modules are:
- [StatusQ.Core](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Core/qmldir) - [StatusQ.Core](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Core/qmldir)
- [StatusQ.Core.Theme](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Core/Theme/qmldir) - [StatusQ.Core.Theme](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Core/Theme/qmldir)
- [StatusQ.Core.Utils](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Core/Utils/qmldir)
- [StatusQ.Components](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Controls/qmldir) - [StatusQ.Components](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Controls/qmldir)
- [StatusQ.Controls](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Components/qmldir) - [StatusQ.Controls](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Components/qmldir)
- [StatusQ.Layout](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Layout/qmldir) - [StatusQ.Layout](https://github.com/status-im/StatusQ/blob/master/src/StatusQ/Layout/qmldir)

View File

@ -468,6 +468,7 @@ Rectangle {
height: implicitHeight > (leftPanel.height - 64) ? implicitHeight + 8 : leftPanel.height - 64 height: implicitHeight > (leftPanel.height - 64) ? implicitHeight + 8 : leftPanel.height - 64
draggableItems: true draggableItems: true
draggableCategories: false
chatList.model: models.demoCommunityChatListItems chatList.model: models.demoCommunityChatListItems
categoryList.model: models.demoCommunityCategoryItems categoryList.model: models.demoCommunityCategoryItems

View File

@ -113,10 +113,12 @@ CExPynn1gWf9bx498P7/nzPcxEzGExhBdJGYihtAYQlO+tUZvqrPbqeudo5iJGEJjCE15a3VtodH3q2I
ListElement { ListElement {
categoryId: "public" categoryId: "public"
name: "Public" name: "Public"
position: 0
} }
ListElement { ListElement {
categoryId: "dev" categoryId: "dev"
name: "Development" name: "Development"
position: 1
} }
} }

View File

@ -4,6 +4,7 @@ import QtQuick.Controls 2.13 as QC
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
@ -28,18 +29,6 @@ Column {
signal chatItemUnmuted(string id) signal chatItemUnmuted(string id)
signal chatItemReordered(string id, int from, int to) signal chatItemReordered(string id, int from, int to)
function getAbsolutePosition(node) {
var returnPos = {};
returnPos.x = 0;
returnPos.y = 0;
if (node !== undefined && node !== null) {
var parentValue = getAbsolutePosition(node.parent);
returnPos.x = parentValue.x + node.x;
returnPos.y = parentValue.y + node.y;
}
return returnPos;
}
onPopupMenuChanged: { onPopupMenuChanged: {
if (!!popupMenu) { if (!!popupMenu) {
popupMenuSlot.sourceComponent = popupMenu popupMenuSlot.sourceComponent = popupMenu
@ -192,7 +181,7 @@ Column {
id: draggedListItemLoader id: draggedListItemLoader
active: dragSensor.active active: dragSensor.active
sourceComponent: StatusChatListItem { sourceComponent: StatusChatListItem {
property var globalPosition: statusChatList.getAbsolutePosition(draggable) property var globalPosition: Utils.getAbsolutePosition(draggable)
parent: QC.Overlay.overlay parent: QC.Overlay.overlay
sensor.cursorShape: dragSensor.cursorShape sensor.cursorShape: dragSensor.cursorShape
Drag.active: dragSensor.active Drag.active: dragSensor.active

View File

@ -1,6 +1,8 @@
import QtQuick 2.14 import QtQuick 2.14
import QtQuick.Controls 2.14 import QtQml.Models 2.14
import QtQuick.Controls 2.14 as QC
import StatusQ.Core.Utils 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
@ -14,9 +16,10 @@ Item {
property bool showCategoryActionButtons: false property bool showCategoryActionButtons: false
property bool showPopupMenu: true property bool showPopupMenu: true
property alias chatList: statusChatList.chatListItems property alias chatList: statusChatList.chatListItems
property alias categoryList: statusChatListCategories property alias categoryList: delegateModel
property alias sensor: sensor property alias sensor: sensor
property bool draggableItems: false property bool draggableItems: false
property bool draggableCategories: false
property Component categoryPopupMenu property Component categoryPopupMenu
property Component chatListPopupMenu property Component chatListPopupMenu
@ -25,6 +28,7 @@ Item {
signal chatItemSelected(string id) signal chatItemSelected(string id)
signal chatItemUnmuted(string id) signal chatItemUnmuted(string id)
signal chatItemReordered(string categoryId, string chatId, int from, int to) signal chatItemReordered(string categoryId, string chatId, int from, int to)
signal chatListCategoryReordered(string categoryId, int from, int to)
signal categoryAddButtonClicked(string id) signal categoryAddButtonClicked(string id)
onPopupMenuChanged: { onPopupMenuChanged: {
@ -66,14 +70,58 @@ Item {
return !!!model.categoryId return !!!model.categoryId
} }
popupMenu: statusChatListAndCategories.chatListPopupMenu popupMenu: statusChatListAndCategories.chatListPopupMenu
} }
Repeater { DelegateModel {
id: statusChatListCategories id: delegateModel
visible: !!model && model.count > 0
delegate: StatusChatListCategory { delegate: Item {
id: draggable
width: statusChatListCategory.width
height: statusChatListCategory.height
property alias chatListCategory: statusChatListCategory
StatusChatListCategory {
id: statusChatListCategory
property bool dragActive: false
property real startY: 0
property real startX: 0
opacity: dragActive ? 0.0 : 1.0
dragSensor.drag.target: draggedListCategoryLoader.item
dragSensor.drag.threshold: 0.1
dragSensor.drag.filterChildren: true
dragSensor.onPressAndHold: {
if (statusChatListAndCategories.draggableCategories) {
dragActive = true
}
}
dragSensor.onReleased: {
if (dragActive) {
statusChatListAndCategories.chatListCategoryReordered(statusChatListCategory.categoryId, statusChatListCategory.originalOrder, statusChatListCategory.originalOrder)
}
dragActive = false
}
dragSensor.cursorShape: dragActive ? Qt.ClosedHandCursor : Qt.PointingHandCursor
dragSensor.onPressed: {
startY = dragSensor.mouseY
startX = dragSensor.mouseX
}
dragSensor.onMouseYChanged: {
if (statusChatListAndCategories.draggableCategories && (Math.abs(startY - dragSensor.mouseY) > 1) && dragSensor.pressed) {
dragActive = true
}
}
dragSensor.onMouseXChanged: {
if (statusChatListAndCategories.draggableCategories && (Math.abs(startX - dragSensor.mouseX) > 1) && dragSensor.pressed) {
dragActive = true
}
}
originalOrder: model.position
categoryId: model.categoryId categoryId: model.categoryId
name: model.name name: model.name
showActionButtons: statusChatListAndCategories.showCategoryActionButtons showActionButtons: statusChatListAndCategories.showCategoryActionButtons
@ -89,6 +137,63 @@ Item {
popupMenu: statusChatListAndCategories.categoryPopupMenu popupMenu: statusChatListAndCategories.categoryPopupMenu
chatListPopupMenu: statusChatListAndCategories.chatListPopupMenu chatListPopupMenu: statusChatListAndCategories.chatListPopupMenu
} }
DropArea {
id: dropArea
width: draggable.chatListCategory.dragActive ? 0 : parent.width
height: draggable.chatListCategory.dragActive ? 0 : parent.height
keys: ["chat-category"]
onEntered: reorderDelay.start()
onDropped: statusChatListAndCategories.chatListCategoryReordered(statusChatListCategory.categoryId, drag.source.originalOrder, statusChatListCategory.DelegateModel.itemsIndex)
Timer {
id: reorderDelay
interval: 100
repeat: false
onTriggered: {
if (dropArea.containsDrag) {
dropArea.drag.source.chatListCategory.originalOrder = statusChatListCategory.originalOrder
delegateModel.items.move(dropArea.drag.source.DelegateModel.itemsIndex, draggable.DelegateModel.itemsIndex)
}
}
}
}
Loader {
id: draggedListCategoryLoader
active: draggable.chatListCategory.dragActive
sourceComponent: StatusChatListCategory {
property var globalPosition: Utils.getAbsolutePosition(draggable)
parent: QC.Overlay.overlay
dragSensor.cursorShape: draggable.chatListCategory.dragSensor.cursorShape
Drag.active: draggable.chatListCategory.dragActive
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Drag.keys: ["chat-category"]
Drag.source: draggable
Component.onCompleted: {
x = globalPosition.x
y = globalPosition.y
}
dragged: true
categoryId: draggable.chatListCategory.categoryId
name: draggable.chatListCategory.name
showActionButtons: draggable.chatListCategory.showActionButtons
chatList.chatListItems.model: draggable.chatListCategory.chatList.chatListItems.model
chatList.selectedChatId: draggable.chatListCategory.chatList.selectedChatId
}
}
}
}
Repeater {
id: statusChatListCategories
visible: !!model && model.count > 0
model: delegateModel
} }
} }
} }

View File

@ -7,16 +7,21 @@ Column {
id: statusChatListCategory id: statusChatListCategory
spacing: 0 spacing: 0
opacity: dragged ? 0.5 : 1
objectName: "chatListCategory"
property int originalOrder: -1
property string categoryId: "" property string categoryId: ""
property string name: "" property string name: ""
property bool opened: true property bool opened: true
property bool dragged: false
property alias showActionButtons: statusChatListCategoryItem.showActionButtons property alias showActionButtons: statusChatListCategoryItem.showActionButtons
property alias addButton: statusChatListCategoryItem.addButton property alias addButton: statusChatListCategoryItem.addButton
property alias menuButton: statusChatListCategoryItem.menuButton property alias menuButton: statusChatListCategoryItem.menuButton
property alias toggleButton: statusChatListCategoryItem.toggleButton property alias toggleButton: statusChatListCategoryItem.toggleButton
property alias chatList: statusChatList property alias chatList: statusChatList
property alias dragSensor: statusChatListCategoryItem.sensor
property Component chatListPopupMenu property Component chatListPopupMenu
property Component popupMenu property Component popupMenu
@ -31,9 +36,11 @@ Column {
id: statusChatListCategoryItem id: statusChatListCategoryItem
title: statusChatListCategory.name title: statusChatListCategory.name
opened: statusChatListCategory.opened opened: statusChatListCategory.opened
sensor.pressAndHoldInterval: 150
showMenuButton: showActionButtons && !!statusChatListCategory.popupMenu showMenuButton: showActionButtons && !!statusChatListCategory.popupMenu
highlighted: statusChatListCategory.dragged
sensor.onClicked: { sensor.onClicked: {
if (mouse.button === Qt.RightButton && showActionButtons && !!statusChatListCategory.popupMenu) { if (mouse.button === Qt.RightButton && showActionButtons && !!statusChatListCategory.popupMenu) {
highlighted = true highlighted = true

View File

@ -0,0 +1,19 @@
pragma Singleton
import QtQuick 2.13
QtObject {
function getAbsolutePosition(node) {
var returnPos = {};
returnPos.x = 0;
returnPos.y = 0;
if (node !== undefined && node !== null) {
var parentValue = getAbsolutePosition(node.parent);
returnPos.x = parentValue.x + node.x;
returnPos.y = parentValue.y + node.y;
}
return returnPos;
}
}

View File

@ -0,0 +1,4 @@
module StatusQ.Core.Utils
singleton Utils 0.1 Utils.qml

View File

@ -7,6 +7,7 @@
<file>src/StatusQ/Core/Theme/Theme.qml</file> <file>src/StatusQ/Core/Theme/Theme.qml</file>
<file>src/StatusQ/Core/Theme/qmldir</file> <file>src/StatusQ/Core/Theme/qmldir</file>
<file>src/StatusQ/Core/Theme/StatusColors.qml</file> <file>src/StatusQ/Core/Theme/StatusColors.qml</file>
<file>src/StatusQ/Core/Utils/Utils.qml</file>
<file>src/StatusQ/Core/StatusIcon.qml</file> <file>src/StatusQ/Core/StatusIcon.qml</file>
<file>src/StatusQ/Core/StatusImageSettings.qml</file> <file>src/StatusQ/Core/StatusImageSettings.qml</file>
<file>src/StatusQ/Core/StatusIconSettings.qml</file> <file>src/StatusQ/Core/StatusIconSettings.qml</file>