diff --git a/containments/homescreens/folio/delegatetoucharea.cpp b/containments/homescreens/folio/delegatetoucharea.cpp index a4c92980..534a8f24 100644 --- a/containments/homescreens/folio/delegatetoucharea.cpp +++ b/containments/homescreens/folio/delegatetoucharea.cpp @@ -55,6 +55,19 @@ void DelegateTouchArea::setHovered(bool hovered) } } +bool DelegateTouchArea::dragging() +{ + return m_dragging; +} + +void DelegateTouchArea::setDragging(bool dragging) +{ + if (dragging != m_dragging) { + m_dragging = dragging; + Q_EMIT draggingChanged(); + } +} + Qt::CursorShape DelegateTouchArea::cursorShape() { return cursor().shape(); @@ -112,6 +125,8 @@ void DelegateTouchArea::mouseUngrabEvent() { if (m_pressed) { handleReleaseEvent(nullptr, false); + } else { + setDragging(false); } QQuickItem::mouseUngrabEvent(); } @@ -151,6 +166,8 @@ void DelegateTouchArea::touchUngrabEvent() { if (m_pressed) { handleReleaseEvent(nullptr, false); + } else { + setDragging(false); } QQuickItem::touchUngrabEvent(); } @@ -194,6 +211,7 @@ void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click) Q_UNUSED(event) bool wasPressed = m_pressed; setPressed(false); + setDragging(false); if (!m_pressAndHeld && click && wasPressed) { Q_EMIT clicked(); @@ -213,6 +231,11 @@ void DelegateTouchArea::handleMoveEvent(QPointerEvent *event, QPointF point) if (QPointF(point - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) { m_pressAndHoldTimer->stop(); setPressed(false); + + if (!m_pressAndHeld) { + setDragging(true); + Q_EMIT dragMoved(point.x() - m_mouseDownPosition.x()); + } } } diff --git a/containments/homescreens/folio/delegatetoucharea.h b/containments/homescreens/folio/delegatetoucharea.h index b87f84f7..1bc11dd5 100644 --- a/containments/homescreens/folio/delegatetoucharea.h +++ b/containments/homescreens/folio/delegatetoucharea.h @@ -20,6 +20,7 @@ class DelegateTouchArea : public QQuickItem Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged FINAL) Q_PROPERTY(bool hovered READ hovered NOTIFY hoveredChanged FINAL) + Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged FINAL) Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape RESET unsetCursor NOTIFY cursorShapeChanged FINAL) Q_PROPERTY(QPointF pressPosition READ pressPosition NOTIFY pressPositionChanged FINAL) @@ -30,6 +31,7 @@ public: bool pressed(); bool hovered(); + bool dragging(); Qt::CursorShape cursorShape(); void setCursorShape(Qt::CursorShape cursorShape); void unsetCursor(); @@ -40,6 +42,8 @@ Q_SIGNALS: void rightMousePress(); void pressAndHold(); void pressAndHoldReleased(); + void draggingChanged(); + void dragMoved(qreal deltaX); void pressedChanged(bool pressed); void hoveredChanged(bool hovered); void cursorShapeChanged(); @@ -69,6 +73,7 @@ private: bool m_pressed{false}; bool m_hovered{false}; + bool m_dragging{false}; bool m_pressAndHeld{false}; Qt::CursorShape m_cursorShape{Qt::ArrowCursor}; QPointF m_mouseDownPosition{}; diff --git a/containments/homescreens/folio/favouritesmodel.h b/containments/homescreens/folio/favouritesmodel.h index af94bda4..fde7db51 100644 --- a/containments/homescreens/folio/favouritesmodel.h +++ b/containments/homescreens/folio/favouritesmodel.h @@ -44,7 +44,7 @@ public: Q_INVOKABLE void removeEntry(int row); Q_INVOKABLE bool addApplication(const QString &storageId); Q_INVOKABLE bool containsApplication(const QString &storageId) const; - void moveEntry(int fromRow, int toRow); + Q_INVOKABLE void moveEntry(int fromRow, int toRow); bool canAddEntry(int row, std::shared_ptr delegate); bool addEntry(int row, std::shared_ptr delegate); std::shared_ptr getEntryAt(int row); diff --git a/containments/homescreens/folio/qml/FavouritesBar.qml b/containments/homescreens/folio/qml/FavouritesBar.qml index bfa0b55d..f27ca865 100644 --- a/containments/homescreens/folio/qml/FavouritesBar.qml +++ b/containments/homescreens/folio/qml/FavouritesBar.qml @@ -46,6 +46,15 @@ MouseArea { // Thumbnail popup hover tracking property int hoveredTaskIndex: -1 + // Drag-reorder state (convergence mode only) + property int dragReorderIndex: -1 + property real dragReorderOffset: 0 + readonly property int dragTargetIndex: { + if (dragReorderIndex === -1) return -1 + let shift = Math.round(dragReorderOffset / dockCellWidth) + return Math.max(0, Math.min(repeater.count - 1, dragReorderIndex + shift)) + } + // Home button (convergence mode, left end) Rectangle { id: homeButton @@ -192,7 +201,25 @@ MouseArea { // multiply the 'fromCenterValue' by the cell size to get the actual position readonly property int centerPosition: (isLocationBottom ? root.dockCellWidth : root.dockCellHeight) * fromCenterValue - x: isLocationBottom ? centerPosition + root.dockCenterX : (parent.width - width) / 2 + // Visual shift during drag-reorder: dragged item follows cursor, + // displaced items slide to make room. + property real dragVisualShift: { + if (root.dragReorderIndex === -1) return 0 + if (delegate.index === root.dragReorderIndex) return root.dragReorderOffset + let targetIdx = root.dragTargetIndex + let myIdx = delegate.index + let dragIdx = root.dragReorderIndex + let cellW = root.dockCellWidth + if (targetIdx > dragIdx && myIdx > dragIdx && myIdx <= targetIdx) return -cellW + if (targetIdx < dragIdx && myIdx >= targetIdx && myIdx < dragIdx) return cellW + return 0 + } + Behavior on dragVisualShift { + enabled: root.dragReorderIndex !== -1 && delegate.index !== root.dragReorderIndex + NumberAnimation { duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad } + } + + x: (isLocationBottom ? centerPosition + root.dockCenterX : (parent.width - width) / 2) + dragVisualShift y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight implicitWidth: root.dockCellWidth @@ -296,18 +323,47 @@ MouseArea { // don't show label in drag and drop mode labelOpacity: delegate.opacity + // Convergence drag-reorder: click-and-drag to reorder + onDraggingChanged: { + if (root.convergenceMode && !folio.FolioSettings.lockLayout) { + if (appDelegate.dragging) { + contextMenu.close() + root.dragReorderIndex = delegate.index + root.dragReorderOffset = 0 + } else { + let from = root.dragReorderIndex + let to = root.dragTargetIndex + root.dragReorderIndex = -1 + root.dragReorderOffset = 0 + if (from !== -1 && to !== -1 && from !== to) { + folio.FavouritesModel.moveEntry(from, to) + } + } + } + } + + onDragMoved: (deltaX) => { + if (root.convergenceMode && !folio.FolioSettings.lockLayout) { + root.dragReorderOffset = deltaX + } + } + onPressAndHold: { // prevent editing if lock layout is enabled if (folio.FolioSettings.lockLayout) return; - let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appDelegate.delegateItem); - folio.HomeScreenState.startDelegateFavouritesDrag( - mappedCoords.x, - mappedCoords.y, - appDelegate.pressPosition.x, - appDelegate.pressPosition.y, - delegate.index - ); + // In convergence mode, drag-reorder is handled by DragHandler; + // only open the context menu on press-and-hold. + if (!root.convergenceMode) { + let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appDelegate.delegateItem); + folio.HomeScreenState.startDelegateFavouritesDrag( + mappedCoords.x, + mappedCoords.y, + appDelegate.pressPosition.x, + appDelegate.pressPosition.y, + delegate.index + ); + } contextMenu.open(); haptics.buttonVibrate(); @@ -315,7 +371,7 @@ MouseArea { onPressAndHoldReleased: { // cancel the event if the delegate is not dragged - if (folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) { + if (!root.convergenceMode && folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) { homeScreen.cancelDelegateDrag(); } } @@ -380,15 +436,45 @@ MouseArea { folio.HomeScreenState.openFolder(pos.x, pos.y, delegate.delegateModel.folder); } + // Convergence drag-reorder: click-and-drag to reorder + onDraggingChanged: { + if (root.convergenceMode && !folio.FolioSettings.lockLayout) { + if (appFolderDelegate.dragging) { + contextMenu.close() + root.dragReorderIndex = delegate.index + root.dragReorderOffset = 0 + } else { + let from = root.dragReorderIndex + let to = root.dragTargetIndex + root.dragReorderIndex = -1 + root.dragReorderOffset = 0 + if (from !== -1 && to !== -1 && from !== to) { + folio.FavouritesModel.moveEntry(from, to) + } + } + } + } + + onDragMoved: (deltaX) => { + if (root.convergenceMode && !folio.FolioSettings.lockLayout) { + root.dragReorderOffset = deltaX + } + } + onPressAndHold: { - let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appFolderDelegate.delegateItem); - folio.HomeScreenState.startDelegateFavouritesDrag( - mappedCoords.x, - mappedCoords.y, - appFolderDelegate.pressPosition.x, - appFolderDelegate.pressPosition.y, - delegate.index - ); + // prevent editing if lock layout is enabled + if (folio.FolioSettings.lockLayout) return; + + if (!root.convergenceMode) { + let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appFolderDelegate.delegateItem); + folio.HomeScreenState.startDelegateFavouritesDrag( + mappedCoords.x, + mappedCoords.y, + appFolderDelegate.pressPosition.x, + appFolderDelegate.pressPosition.y, + delegate.index + ); + } contextMenu.open(); haptics.buttonVibrate(); @@ -396,7 +482,7 @@ MouseArea { onPressAndHoldReleased: { // cancel the event if the delegate is not dragged - if (folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) { + if (!root.convergenceMode && folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) { root.homeScreen.cancelDelegateDrag(); } }