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()
|
Qt::CursorShape DelegateTouchArea::cursorShape()
|
||||||
{
|
{
|
||||||
return cursor().shape();
|
return cursor().shape();
|
||||||
|
|
@ -112,6 +125,8 @@ void DelegateTouchArea::mouseUngrabEvent()
|
||||||
{
|
{
|
||||||
if (m_pressed) {
|
if (m_pressed) {
|
||||||
handleReleaseEvent(nullptr, false);
|
handleReleaseEvent(nullptr, false);
|
||||||
|
} else {
|
||||||
|
setDragging(false);
|
||||||
}
|
}
|
||||||
QQuickItem::mouseUngrabEvent();
|
QQuickItem::mouseUngrabEvent();
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +166,8 @@ void DelegateTouchArea::touchUngrabEvent()
|
||||||
{
|
{
|
||||||
if (m_pressed) {
|
if (m_pressed) {
|
||||||
handleReleaseEvent(nullptr, false);
|
handleReleaseEvent(nullptr, false);
|
||||||
|
} else {
|
||||||
|
setDragging(false);
|
||||||
}
|
}
|
||||||
QQuickItem::touchUngrabEvent();
|
QQuickItem::touchUngrabEvent();
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +211,7 @@ void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click)
|
||||||
Q_UNUSED(event)
|
Q_UNUSED(event)
|
||||||
bool wasPressed = m_pressed;
|
bool wasPressed = m_pressed;
|
||||||
setPressed(false);
|
setPressed(false);
|
||||||
|
setDragging(false);
|
||||||
|
|
||||||
if (!m_pressAndHeld && click && wasPressed) {
|
if (!m_pressAndHeld && click && wasPressed) {
|
||||||
Q_EMIT clicked();
|
Q_EMIT clicked();
|
||||||
|
|
@ -213,6 +231,11 @@ void DelegateTouchArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||||
if (QPointF(point - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
|
if (QPointF(point - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
|
||||||
m_pressAndHoldTimer->stop();
|
m_pressAndHoldTimer->stop();
|
||||||
setPressed(false);
|
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 pressed READ pressed NOTIFY pressedChanged FINAL)
|
||||||
Q_PROPERTY(bool hovered READ hovered NOTIFY hoveredChanged 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(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape RESET unsetCursor NOTIFY cursorShapeChanged FINAL)
|
||||||
Q_PROPERTY(QPointF pressPosition READ pressPosition NOTIFY pressPositionChanged FINAL)
|
Q_PROPERTY(QPointF pressPosition READ pressPosition NOTIFY pressPositionChanged FINAL)
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ public:
|
||||||
|
|
||||||
bool pressed();
|
bool pressed();
|
||||||
bool hovered();
|
bool hovered();
|
||||||
|
bool dragging();
|
||||||
Qt::CursorShape cursorShape();
|
Qt::CursorShape cursorShape();
|
||||||
void setCursorShape(Qt::CursorShape cursorShape);
|
void setCursorShape(Qt::CursorShape cursorShape);
|
||||||
void unsetCursor();
|
void unsetCursor();
|
||||||
|
|
@ -40,6 +42,8 @@ Q_SIGNALS:
|
||||||
void rightMousePress();
|
void rightMousePress();
|
||||||
void pressAndHold();
|
void pressAndHold();
|
||||||
void pressAndHoldReleased();
|
void pressAndHoldReleased();
|
||||||
|
void draggingChanged();
|
||||||
|
void dragMoved(qreal deltaX);
|
||||||
void pressedChanged(bool pressed);
|
void pressedChanged(bool pressed);
|
||||||
void hoveredChanged(bool hovered);
|
void hoveredChanged(bool hovered);
|
||||||
void cursorShapeChanged();
|
void cursorShapeChanged();
|
||||||
|
|
@ -69,6 +73,7 @@ private:
|
||||||
|
|
||||||
bool m_pressed{false};
|
bool m_pressed{false};
|
||||||
bool m_hovered{false};
|
bool m_hovered{false};
|
||||||
|
bool m_dragging{false};
|
||||||
bool m_pressAndHeld{false};
|
bool m_pressAndHeld{false};
|
||||||
Qt::CursorShape m_cursorShape{Qt::ArrowCursor};
|
Qt::CursorShape m_cursorShape{Qt::ArrowCursor};
|
||||||
QPointF m_mouseDownPosition{};
|
QPointF m_mouseDownPosition{};
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ public:
|
||||||
Q_INVOKABLE void removeEntry(int row);
|
Q_INVOKABLE void removeEntry(int row);
|
||||||
Q_INVOKABLE bool addApplication(const QString &storageId);
|
Q_INVOKABLE bool addApplication(const QString &storageId);
|
||||||
Q_INVOKABLE bool containsApplication(const QString &storageId) const;
|
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 canAddEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
||||||
bool addEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
bool addEntry(int row, std::shared_ptr<FolioDelegate> delegate);
|
||||||
std::shared_ptr<FolioDelegate> getEntryAt(int row);
|
std::shared_ptr<FolioDelegate> getEntryAt(int row);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,15 @@ MouseArea {
|
||||||
// Thumbnail popup hover tracking
|
// Thumbnail popup hover tracking
|
||||||
property int hoveredTaskIndex: -1
|
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)
|
// Home button (convergence mode, left end)
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: homeButton
|
id: homeButton
|
||||||
|
|
@ -192,7 +201,25 @@ MouseArea {
|
||||||
// multiply the 'fromCenterValue' by the cell size to get the actual position
|
// multiply the 'fromCenterValue' by the cell size to get the actual position
|
||||||
readonly property int centerPosition: (isLocationBottom ? root.dockCellWidth : root.dockCellHeight) * fromCenterValue
|
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
|
y: isLocationBottom ? (parent.height - height) / 2 : parent.height / 2 - centerPosition - root.dockCellHeight
|
||||||
|
|
||||||
implicitWidth: root.dockCellWidth
|
implicitWidth: root.dockCellWidth
|
||||||
|
|
@ -296,18 +323,47 @@ MouseArea {
|
||||||
// don't show label in drag and drop mode
|
// don't show label in drag and drop mode
|
||||||
labelOpacity: delegate.opacity
|
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: {
|
onPressAndHold: {
|
||||||
// prevent editing if lock layout is enabled
|
// prevent editing if lock layout is enabled
|
||||||
if (folio.FolioSettings.lockLayout) return;
|
if (folio.FolioSettings.lockLayout) return;
|
||||||
|
|
||||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appDelegate.delegateItem);
|
// In convergence mode, drag-reorder is handled by DragHandler;
|
||||||
folio.HomeScreenState.startDelegateFavouritesDrag(
|
// only open the context menu on press-and-hold.
|
||||||
mappedCoords.x,
|
if (!root.convergenceMode) {
|
||||||
mappedCoords.y,
|
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appDelegate.delegateItem);
|
||||||
appDelegate.pressPosition.x,
|
folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||||
appDelegate.pressPosition.y,
|
mappedCoords.x,
|
||||||
delegate.index
|
mappedCoords.y,
|
||||||
);
|
appDelegate.pressPosition.x,
|
||||||
|
appDelegate.pressPosition.y,
|
||||||
|
delegate.index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
contextMenu.open();
|
contextMenu.open();
|
||||||
haptics.buttonVibrate();
|
haptics.buttonVibrate();
|
||||||
|
|
@ -315,7 +371,7 @@ MouseArea {
|
||||||
|
|
||||||
onPressAndHoldReleased: {
|
onPressAndHoldReleased: {
|
||||||
// cancel the event if the delegate is not dragged
|
// 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();
|
homeScreen.cancelDelegateDrag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -380,15 +436,45 @@ MouseArea {
|
||||||
folio.HomeScreenState.openFolder(pos.x, pos.y, delegate.delegateModel.folder);
|
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: {
|
onPressAndHold: {
|
||||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appFolderDelegate.delegateItem);
|
// prevent editing if lock layout is enabled
|
||||||
folio.HomeScreenState.startDelegateFavouritesDrag(
|
if (folio.FolioSettings.lockLayout) return;
|
||||||
mappedCoords.x,
|
|
||||||
mappedCoords.y,
|
if (!root.convergenceMode) {
|
||||||
appFolderDelegate.pressPosition.x,
|
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.delegateModel, appFolderDelegate.delegateItem);
|
||||||
appFolderDelegate.pressPosition.y,
|
folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||||
delegate.index
|
mappedCoords.x,
|
||||||
);
|
mappedCoords.y,
|
||||||
|
appFolderDelegate.pressPosition.x,
|
||||||
|
appFolderDelegate.pressPosition.y,
|
||||||
|
delegate.index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
contextMenu.open();
|
contextMenu.open();
|
||||||
haptics.buttonVibrate();
|
haptics.buttonVibrate();
|
||||||
|
|
@ -396,7 +482,7 @@ MouseArea {
|
||||||
|
|
||||||
onPressAndHoldReleased: {
|
onPressAndHoldReleased: {
|
||||||
// cancel the event if the delegate is not dragged
|
// 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();
|
root.homeScreen.cancelDelegateDrag();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue