Skip to content

Commit dcfe439

Browse files
committed
Drag&drop: Seed Move op at drag start to remove same-volume "+" flicker
Both `performSingleFileDrag` and `performSelectionDrag` now push `'move'` via `setSelfDragResolvedOperation` right before the native `startDrag`. So the very first `draggingEntered:` already returns Move and the OS draws no "+" badge for the same-volume case (the default and most common path). For cross-volume drags the first `handleDragOver` flips the op to Copy, so the "+" appears ~5–30ms later. Picked this direction over the reverse because a badge appearing late feels intentional, while a badge appearing-then-disappearing reads as a glitch. Updated the gotcha in `drag/CLAUDE.md` to describe the new behavior.
1 parent 7c8a19d commit dcfe439

2 files changed

Lines changed: 24 additions & 8 deletions

File tree

apps/desktop/src/lib/file-explorer/drag/CLAUDE.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,13 @@ Key files:
169169
> 1 `NSDraggingItem`s on the pasteboard. Even when the OS image is swapped to transparent for self-drags, the badges
170170
> still draw because they're separate sprites near the cursor, not painted onto the image surface. Don't try to
171171
> replace or skin them — invest custom branding into `DragOverlay.svelte` instead, which is fully under our control.
172-
- **Gotcha**: The "+" badge may briefly flash on the first frame of a self-drag
173-
- **Why**: Our swizzle overrides the wry-default `Copy` return only after the frontend has pushed a resolved op via
174-
`setSelfDragResolvedOperation`. The very first `draggingEntered:` can run before that IPC lands, so macOS can show
175-
"+" for a frame or two before flipping to the correct op on the next `draggingUpdated:`. Visible as a tiny flicker
176-
on the very first drag start, not on subsequent updates. To eliminate it entirely we'd need to push an initial
177-
"best-guess" op (likely `Move`, since same-volume default is Move) right before `startDrag`. Not done yet because
178-
the flicker is nearly imperceptible in practice.
172+
- **Gotcha**: For cross-volume self-drags, the "+" badge may appear ~1–2 frames late
173+
- **Why**: Both `performSingleFileDrag` and `performSelectionDrag` seed the swizzle with `'move'` via
174+
`setSelfDragResolvedOperation` right before `startDrag`, so the same-volume case (the default, most common) shows no
175+
"+" from frame one. For cross-volume drags the resolved op is `'copy'`, but JS only learns the target volume after
176+
the first `handleDragOver`, so the badge flips from "no +" to "+" on the next `draggingUpdated:` — a slight "+"
177+
appearing late, ~5–30ms. Picked this direction over the reverse because a badge appearing later feels intentional,
178+
while a badge appearing-then-disappearing reads as a glitch.
179179

180180
## Platform support
181181

apps/desktop/src/lib/file-explorer/drag/drag-drop.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818

1919
import { tempDir, join } from '@tauri-apps/api/path'
2020
import { getCachedIcon } from '$lib/icon-cache'
21-
import { startSelectionDrag, startDragPaths, prepareSelfDragOverlay, clearSelfDragOverlay } from '$lib/tauri-commands'
21+
import {
22+
startSelectionDrag,
23+
startDragPaths,
24+
prepareSelfDragOverlay,
25+
clearSelfDragOverlay,
26+
setSelfDragResolvedOperation,
27+
} from '$lib/tauri-commands'
2228
import { getSetting } from '$lib/settings/settings-store'
2329
import { cancelClickToRename } from '../rename/rename-activation'
2430
import { renderDragImage } from './drag-image-renderer'
@@ -365,6 +371,13 @@ async function performSingleFileDrag(filePath: string, iconId: string, fileInfo?
365371
// Store rich image path so native swizzle can swap to it on window exit
366372
await prepareSelfDragOverlay(resolved.path)
367373

374+
// Seed the swizzle with our best-guess op so the very first draggingEntered:
375+
// returns Move (no badge) instead of wry's hardcoded Copy ("+"). 'move' wins
376+
// over 'copy' as the default because it's the same-volume case (most common)
377+
// and because a "+" appearing later feels intentional, while a "+" disappearing
378+
// would feel like a glitch.
379+
await setSelfDragResolvedOperation('move')
380+
368381
// Don't reset draggingFromSelf after the start call — it resolves before the
369382
// OS delivers drop/leave events. The flag is cleared by the drop handler.
370383
draggingFromSelf = true
@@ -386,6 +399,9 @@ async function performSelectionDrag(context: SelectionDragContext): Promise<void
386399
// Store rich image path so native swizzle can swap to it on window exit
387400
await prepareSelfDragOverlay(resolved.path)
388401

402+
// Seed the swizzle with our best-guess op — see performSingleFileDrag comment.
403+
await setSelfDragResolvedOperation('move')
404+
389405
// Don't reset draggingFromSelf after startDrag — see performSingleFileDrag comment.
390406
draggingFromSelf = true
391407
await startSelectionDrag(context.listingId, context.indices, context.includeHidden, context.hasParent, resolved.path)

0 commit comments

Comments
 (0)