Watch
1
0
Fork
You've already forked souveraine
0

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:
Fimeg 2026-07-22 21:11:19 -04:00
commit 132d8134f2
4 changed files with 77 additions and 231 deletions

View file

@ -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
}
}
}

View file

@ -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
}