Repository navigation
feat(drag): steady drop places, groups on a held hover, group changes that hand slots over - #70
Merged
Conversation
…ds its width A drag inside the dock used to leave the element in its slot at a third of its opacity and open a narrow gap with an accent line where it would land, so the dock grew while dragging; on the drop the element popped out of one place and into the other, a running app pinned in the run left with its exit animation and came back with its entrance, and the card widened and narrowed as the two raced. A centred card that changes width also moves every tile under a pointer that stands still, so the drop target flickered and the faded tile lagged behind the hand. Now the element is picked up: a copy of it (a ShaderEffectSource with hideSource, label and plate included) follows the pointer at full opacity, and the drop gap opens as wide as the element, without the line. The held element's slot gives up exactly the room the open drop gaps take (DockLift.gapSum, which each DropGap reports, a spacer's row spacing included), down to nothing with the spacing after it (a negative width in the Row). The neighbours slide to fill the place it left while the row parts where it would land, and the card keeps its width throughout: over another app (to group), over the running apps or pulled off the dock no gap opens and the place stays empty. On the drop the row takes its final shape in one step (DockLift.snapping turns off the gaps' easing): a slot entering the pinned run stands open from its first frame (at least the width the gap held, as its label settles a frame later) and names and side marks do not ease while a drag holds or settles; a held slot leaving its run (into the pinned run, a group or off the dock) eases shut from the room it had left. The copy settles into the element's new place before the element shows there, or fades into a group or off the dock. A drag that ends without a drop eases back: the gap closes, the place reopens by the same amount, and the copy returns home. Motion Off does all of it at once. Apps, app groups and folders lift; drags out of a group popup keep the narrow gap, as nothing in the row stands in for them.
…, dragging out Making, filling, emptying and dissolving a group were the weak spots of the lifted drag. A new group grew beside the app it replaced while that app shrank (opposite curves, so the dock's width overshot), an app dropped into a group faded where it was let go, an app dragged out of an opened group had no copy and the dock's old narrow gap, and the last app of a group it left jumped to the end of the pins. - Slot hand-over (DockLift.handOff/handOn, RowSlot): an element that takes others' place starts as wide as the room they leave and eases to its own width, so the dock moves one way. A group made on an app swaps in whole in that app's slot; Ungroup's apps share the group's room and open out of it. Hand-overs nest, and each new slot takes its own once it settles. - The empty room a dropped element leaves closes on the same curve the slots taking its room open on (and at once without layout motion). - Dropped on an app or a group, the copy flies into the group's tile, shrinking to a member's size, and the tile pulses as it takes it. - Dragged out of an opened group, the popup fades but stays as the drag's surface; the app's icon follows the pointer (LiftIcon), drawn in the dock and over the popup at the same place, the gap opens one tile wide and the room it holds goes to the app's new slot on the drop. Let go over its own group, the icon flies back in. - The last app of a group dissolves into the group's place, not the end of the pins (removeAppFromGroup, a drag or the menu alike).
… held hover Each neighbour had three zones (an insert edge, a 76% group middle, the other edge), so dragging across one opened a gap before it, closed it over its middle and opened it again after it: the icons jumped back and forth under the copy. The gap now stays where it is until the pointer has gone three quarters of the way over the next element (or back over the previous one); that element then moves once, into the place the gap leaves and away from the pointer, which also keeps it from flickering at the boundary. Folders and group tiles move the same way. Over the middle half of an app or a group nothing moves. Held there for half a second (DockLift.dwell) it becomes the target and its halo shows; only then does a drop make or fill a group. Let go before, the element lands where its gap stands, which is what the dock shows.
priard
added a commit
to priard/omadock
that referenced
this pull request
Oct 15, 2026
Hidden dividers, the lifted drag and group motion are open upstream; CLAUDE.md's open-PR list was from before v4.1.0.
Owner
|
Thank you @priard — merged into v4.7.0! The three-quarter steady-drop threshold plus the 500ms single-shot dwell halo (groups form on held hover, never by accident) resolves the back-and-forth icon dance elegantly, and both timers are single-shot so the zero-CPU invariant holds. Verified on experimental: 254 Node + 116 Python tests, qmllint 0 errors, structure-check PASSED. |
priard
added a commit
to priard/omadock
that referenced
this pull request
Oct 18, 2026
Upstream v4.7.0 ships the hidden dividers, the drag lift and the group motion; the fork keeps its own version suffix on top.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Builds on #69 (the lifted drag). Two things were still weak:
What it does
Neighbours move once. The gap stays where it is until the pointer has gone three quarters of the way over the next element (or back over the previous one). That element then moves once, into the place the gap leaves and away from the pointer, so it does not flicker at the boundary. Pinned apps, group tiles, apps out of a group popup and folders all move this way.
Groups form on a held hover. Over the middle half of an app or a group nothing moves. Held there for half a second, it becomes the target and its halo shows; only then does a drop make or fill a group. Let go before, the element lands where its gap stands, which is what the dock shows.
Group changes hand slots over. An element that takes others' place starts as wide as the room they leave and eases to its own width, so the dock's width moves one way:
Into a group. Dropped on an app or a group, the copy flies into the group's tile, shrinking to a member's size, and the tile pulses as it takes it.
Out of a group. Dragged out of an opened group, the popup fades but stays as the drag's surface until the button is let go. The app's icon follows the pointer, drawn in the dock and over the popup at the same place, so it crosses the edge between the two surfaces whole. The gap opens one tile wide, and on the drop the room it holds goes to the app's new slot. Let go over its own group, the icon flies back in.
How
DockLift.handOff(fromKeys, room)/handOn(toKeys),RowSlot): a slot leaving underhandOffadds its width to the open batch and snaps out; the slots named byhandOnstand at an equal share of that room until they settle, then ease to their own width (_base). A single one swaps in whole, without the fade and scale. Batches nest (an app dragged out of a group that dissolves), and each slot takes its own share once it has read it.DockGroupsLogic.handOver(root, keys, change)wraps group creation, Ungroup and the dissolve.DockDragLogic.stickyIndexkeeps the insert index until the pointer passes 75% of the next element or 25% of the previous one. Held hover:DockLift.dwell(key)setsdropTargetAppId/dropTargetGroupIdafter 500 ms on the same candidate. While a target is set the gap stays where it was (DockCard.dropLineRow).DockLift.settle(key, fade, into)heads the copy's icon for the group's icon and scales it around its centre;absorbed(key)makes the slot pulse.DockLift.beginIcondraws the app's icon (LiftIcon, the dock'sDockIconArtsettings) instead of aShaderEffectSource.AppGroupPopupmirrors it on the popup's surface throughDockPopupWindow.fromDockWindow(the inverse oftoDockWindow). The popup's close frame follows the faded body, so it does not flash back.Depends on #69
This is cut from
feat/drag-lift(#69); the diff includes it until it merges. The commits of this PR are the two after it, from "feat(drag): group changes move as one" on. I'll rebase ontoexperimentalonce #69 is in.Verification
node --checkof the model files andnode --test tests/unit/*.test.mjs tests/unit/*.test.js(252 pass)python3 -m unittest discover -s tests/unit -p 'test_*.py'qmllint(no new error kinds)security-grep.py,structure-check.py,shaders-in-sync.sh./tests/manifest-check.sh .andomarchy plugin validate ../tests/smoke-test.shagainst a running dock