dock: add drag-to-reorder for pinned apps alongside existing combine-into-stack
Dragging a pinned app now shows an insertion gap indicator when hovering over other pinned apps. Quick horizontal slide + release = reorder the pinned apps array. Dwell (500ms) on a target still = combine into stack (existing behavior). Both gestures share the same drag start and ghost. TaskbarApps: reorderPinned() splice-moves within Config.options.dock.pinnedApps. DockApps: shared dragSourceIndex/dragInsertIndex state, delegate passes parent.index as modelIndex to DockAppButton. DockAppButton: insertion indicator (2px primary-color line), DropArea onEntered computes gap position based on drag direction, release handler commits reorder when no dwell fired. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9c27504009
commit
132d8134f2
4 changed files with 77 additions and 231 deletions
|
|
@ -13,6 +13,7 @@ DockButton {
|
|||
id: root
|
||||
property var appToplevel
|
||||
property var appListRoot
|
||||
property int modelIndex: -1 // set by the delegate Loader (parent.index)
|
||||
property int lastFocused: -1
|
||||
property real iconSize: 35
|
||||
property real countDotWidth: 10
|
||||
|
|
@ -32,6 +33,25 @@ DockButton {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Insertion gap indicator (drag-to-reorder) ----------------------
|
||||
// A thin vertical line at the LEFT edge of this delegate when it is
|
||||
// the current reorder target. Sits above the button content so it's
|
||||
// always visible during a drag.
|
||||
Rectangle {
|
||||
visible: appListRoot.dragInsertIndex >= 0
|
||||
&& root.modelIndex === appListRoot.dragInsertIndex
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -1 // straddle the ListView spacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 2
|
||||
height: parent.height * 0.65
|
||||
radius: 1
|
||||
color: Appearance.colors.colPrimary
|
||||
z: 10
|
||||
opacity: visible ? 1 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 80 } }
|
||||
}
|
||||
|
||||
// --- Drag-to-combine (Tier 1) --------------------------------------
|
||||
// Pattern copied from this codebase's OverviewWidget.qml (drag a window
|
||||
// onto a workspace): a MouseArea with drag.target moves a ghost Item;
|
||||
|
|
@ -96,6 +116,9 @@ DockButton {
|
|||
// Structural dock edits (manifest pin/stack mutations) are
|
||||
// refused while a drag is in flight — see DockManifest.
|
||||
GlobalStates.dockDragInProgress = true
|
||||
// Store source index for reorder computation.
|
||||
appListRoot.dragSourceIndex = root.modelIndex
|
||||
appListRoot.dragInsertIndex = -1
|
||||
// Hold-then-move means drag, not menu: a menu that opened on
|
||||
// the hold gets dismissed the moment real movement starts.
|
||||
root.menuOpen = false
|
||||
|
|
@ -112,10 +135,21 @@ DockButton {
|
|||
dragging = false
|
||||
GlobalStates.dockDragInProgress = false
|
||||
if (target && target.toLowerCase() !== root.appToplevel.appId.toLowerCase()) {
|
||||
// Dwell fired → combine into stack (existing path).
|
||||
TaskbarApps.combineIntoStack(target, root.appToplevel.appId, targetStack)
|
||||
} else if (appListRoot.dragInsertIndex >= 0
|
||||
&& appListRoot.dragInsertIndex !== appListRoot.dragSourceIndex) {
|
||||
// No dwell, valid insertion gap → reorder pinned app.
|
||||
const stacksCount = TaskbarApps.stacksList().length
|
||||
const targetPinnedIdx = appListRoot.dragInsertIndex - stacksCount
|
||||
if (targetPinnedIdx >= 0) {
|
||||
TaskbarApps.reorderPinned(root.appToplevel.appId, targetPinnedIdx)
|
||||
}
|
||||
}
|
||||
appListRoot.dragTargetAppId = ""
|
||||
appListRoot.dragTargetStackId = ""
|
||||
appListRoot.dragInsertIndex = -1
|
||||
appListRoot.dragSourceIndex = -1
|
||||
return
|
||||
}
|
||||
if (root.menuOpen) return // long-press already handled it
|
||||
|
|
@ -129,6 +163,8 @@ DockButton {
|
|||
dragging = false
|
||||
dragGhost.Drag.active = false
|
||||
GlobalStates.dockDragInProgress = false
|
||||
appListRoot.dragInsertIndex = -1
|
||||
appListRoot.dragSourceIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +173,22 @@ DockButton {
|
|||
onEntered: (drag) => {
|
||||
if (drag.source === root) return
|
||||
dwellTimer.restart()
|
||||
// --- Reorder: compute insertion gap position ---------------
|
||||
// Only pinned apps (not stacks, not running apps) participate
|
||||
// in reordering. The gap appears BEFORE the target if dragging
|
||||
// left (source > target), AFTER if dragging right (source <
|
||||
// target) — matching the physical intuition of sliding an icon
|
||||
// into a new slot.
|
||||
const srcIdx = appListRoot.dragSourceIndex
|
||||
const tgtIdx = root.modelIndex
|
||||
const stacksCount = TaskbarApps.stacksList().length
|
||||
const pinnedCount = Config.options?.dock.pinnedApps?.length ?? 0
|
||||
if (srcIdx >= stacksCount && srcIdx < stacksCount + pinnedCount
|
||||
&& tgtIdx >= stacksCount && tgtIdx < stacksCount + pinnedCount
|
||||
&& srcIdx !== tgtIdx
|
||||
&& root.appToplevel.pinned && !root.appToplevel.isStack) {
|
||||
appListRoot.dragInsertIndex = srcIdx < tgtIdx ? tgtIdx + 1 : tgtIdx
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
dwellTimer.stop()
|
||||
|
|
@ -153,6 +205,8 @@ DockButton {
|
|||
root.formingStack = true
|
||||
appListRoot.dragTargetAppId = root.appToplevel.appId
|
||||
appListRoot.dragTargetStackId = "" // plain app -> new stack
|
||||
// Dwell = combine intent, not reorder — clear the gap.
|
||||
appListRoot.dragInsertIndex = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ Item {
|
|||
// would outgrow the screen. Past it the ListView scrolls horizontally.
|
||||
property real maxWidth: -1
|
||||
|
||||
// Drag-to-reorder state, shared across all DockAppButton delegates.
|
||||
// dragSourceIndex: model index of the button currently being dragged.
|
||||
// dragInsertIndex: model index BEFORE which the insertion gap shows.
|
||||
// -1 means no valid reorder target. Set by DropArea.onEntered when
|
||||
// the drag hovers a pinned app; cleared on release / cancel / dwell.
|
||||
property int dragSourceIndex: -1
|
||||
property int dragInsertIndex: -1
|
||||
|
||||
Layout.fillHeight: true
|
||||
// No top-only margin: it shoved the whole icon list DOWN inside the bar
|
||||
// with nothing balancing it — the actual cause of icons drooping below
|
||||
|
|
@ -67,6 +75,7 @@ Item {
|
|||
}
|
||||
delegate: Loader {
|
||||
required property var modelData
|
||||
required property int index
|
||||
// Fan-out stack entries render as DockStack; everything else
|
||||
// (pinned apps, running windows, separators) as DockAppButton.
|
||||
sourceComponent: modelData.isStack ? stackComp : appComp
|
||||
|
|
@ -76,6 +85,7 @@ Item {
|
|||
DockAppButton {
|
||||
appToplevel: modelData
|
||||
appListRoot: root
|
||||
modelIndex: parent.index
|
||||
topInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding
|
||||
bottomInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue