mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
Add drag-reorder for dock favourites
Click-and-drag reordering of favourites bar items in convergence mode. The existing touch-based press-and-hold drag is kept for mobile; in convergence mode, press-and-hold opens the context menu only. DelegateTouchArea owns the exclusive mouse grab at the C++ level, so drag detection (threshold crossing + delta signals) is added there rather than using a QML DragHandler. Displaced items animate into their new positions while the dragged item follows the cursor. Expose FavouritesModel::moveEntry as Q_INVOKABLE so QML can persist the reorder.
This commit is contained in:
parent
ca6170c1f5
commit
9bccbeede6
4 changed files with 134 additions and 20 deletions
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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<FolioDelegate> delegate);
|
||||
bool addEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
||||
std::shared_ptr<FolioDelegate> getEntryAt(int row);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue