Skip to content

Feat(taskbar): drag to reorder pinned taskbar applications - #3715

Draft
GhostEagle68 wants to merge 1 commit into
noctalia-dev:mainfrom
GhostEagle68:feat/taskbar-drag-reorder
Draft

Feat(taskbar): drag to reorder pinned taskbar applications #3715
GhostEagle68 wants to merge 1 commit into
noctalia-dev:mainfrom
GhostEagle68:feat/taskbar-drag-reorder

Conversation

@GhostEagle68

Copy link
Copy Markdown
Contributor

Summary

Adds drag-to-reorder for pinned tiles in the taskbar widget: press a pinned tile, drag it along the bar, and drop it in a new position. The new order is written to widget.<name>.pinned, so it persists.

How it works:

  • Arming — a drag starts once the pointer travels 8px along the bar while pressed, or after a 300ms hold if you press without moving. Plain clicks, middle-click close, and right-click context menus are unaffected.
  • Visuals — the dragged tile leaves the layout flow (setParticipatesInLayout(false)) so Flex stops repositioning it, and an invisible spacer of the same size is inserted at the target index to hold the drop gap open. Neighbours reflow around it, and the strip keeps its width. On drop the tile is parked in the gap so the strip looks settled while the config write lands.
  • Persistence — on release, the pin list is reordered and written via setOverride, deferred through DeferredCall::callLater because the write rebuilds the taskbar and would otherwise destroy the InputArea whose handler is still running.

Reordering is enabled only when there are at least two pinned entries, the widget has a config name to persist to, and group_by_workspace is off (in that mode the flat pinned order isn't what's on screen).

Motivation

Pinned tiles land in whatever order they were pinned, and the only way to rearrange them is to unpin and re-pin in sequence. Dragging is what people expect from a taskbar.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring
  • Build / packaging

Related Issue

None

Testing

just format
just build
just test

On Hyprland with a horizontal bottom bar and 6 pinned apps: dragging in both directions, dropping at both ends, dropping back on the origin (no write), plain/middle/right clicks on pinned and unpinned tiles, and clicks immediately following a drag. Reordered state verified in settings.toml and after restarting noctalia.

2026-07-30.13-12-06.online-video-cutter.com.mp4

Notice the unpinned apps (VSC and Claude) on the right are not affected.

Manual Coverage

  • Tested on Niri
  • Tested on Hyprland
  • Tested on Sway
  • Tested on another compositor:
  • Tested with different bar positions and density settings
  • Tested at different interface scaling values
  • Tested with multiple monitors

Checklist

  • This PR is ready for review, or it is marked as Draft.
  • I read and followed the relevant guidance in CONTRIBUTING.md.
  • I ran just format with clang-format v22+ installed, or this PR has no code changes.
  • I ran the relevant build or test commands, or explained why they were not run.
  • I self-reviewed the changes.
  • I checked for new warnings or errors.
  • I will update end-user documentation after merge, or this PR does not change user-facing configuration or behavior.
  • I added or updated assets/translations/en.json, or this PR adds no new user-facing strings.
  • I did not edit non-English translation files unless this PR is explicitly for translation tooling, an import/export sync, or a maintainer-requested locale change.
  • I used the existing canonical names for config keys, IPC names, paths, and identifiers.

Additional Notes

Untested paths. Only tested on a horizontal bottom bar with one output. The vertical-bar path is implemented (all geometry is expressed along a "main axis" that follows m_vertical). Also multi-monitor is likewise untested.

No --insertAt correction. Dock::endDrag decrements the insert position when the target is past the source, because its computeDragTargetIndex returns an insertion slot. This one returns a destination index (sourceIndex + lround(travel / pitch), clamped), so after erasing the source, inserting at the target is already correct. Copying the dock's correction here would shift rightward drags one slot short.

Drop latency. There's a ~55ms gap between release and the rebuild, which is setOverride writing the file and running loadAll() — the same cost as any other settings change. The tile is parked in its final position first so this isn't visible, but I mention it in case the config reload path is something you'd want to look at separately.

Disclosure: I used Claude Code as a pair-programming assistant on this feature; for reading unfamiliar parts of the scene graph and layout code, and for review. The design decisions, testing, and verification are all mine.

@GhostEagle68 GhostEagle68 changed the title Feat/taskbar drag reorder Feat(taskbar) drag reorder Jul 30, 2026
@GhostEagle68 GhostEagle68 changed the title Feat(taskbar) drag reorder Feat(taskbar): drag to reorder pinned taskbar applications Jul 30, 2026
@GhostEagle68

Copy link
Copy Markdown
Contributor Author

I do believe that #3635 would be beneficial merged together with this, althoughbot necessary. And I have a local build with that PR and this one and no issues together

@ItsLemmy
ItsLemmy marked this pull request as draft August 15, 2026 18:55
@ItsLemmy

ItsLemmy commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Would need to be rebased and tested on vertical bar.

@virtuallyunknown

Copy link
Copy Markdown

Hey, sorry to bother you, I have a quick question. Would you, be interested in widening the scope of this PR?

I'm basically looking to migrate to Noctalia, but the one thing I really miss is a classic taskbar where you can re-order the windows via dragging and dropping, much like browser tabs.

Your PR is already halfway there, with the exception that it only concerns pinned app buttons (tiles), and there also needs to be some mechanism to inform the underlying compositor about the new order, so that it can arrange it's own windows in the same order that they appear in the taskbar widget (this is only relevant to scrolling wms like Niri).

Here is a very quick demonstration (bottom bar). Waybar + niri_window_buttons

video.mp4

I asked about my issue on the Discord server, where I was made aware of this PR. And yeah, it's probably a long shot, but it doesn't hurt to ask as they say.

Cheers.

@GhostEagle68
GhostEagle68 force-pushed the feat/taskbar-drag-reorder branch from 00f5543 to cb04deb Compare August 16, 2026 05:29
@GhostEagle68

GhostEagle68 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

Would need to be rebased and tested on vertical bar.

@ItsLemmy Rebased and tested both horizontal and vertical

recording_20260815_232116.mp4
recording_20260815_232229.mp4

Edit:
src/dbus/network/network_manager_service.cpp Seems to have missed just format from another commit, nothing here touched that :)

@ItsLemmy

Copy link
Copy Markdown
Collaborator

1. [P1] Drag cleanup incorrectly depends on a config reload

taskbar_widget.cpp:916-924

commitDragReorder() returns true after merely scheduling setOverride(). The release handler then clears m_drag.area and m_drag.spacer, making endDragVisual() a no-op. The tile remains excluded from layout and the spacer remains attached, under the assumption that setOverride() will rebuild the taskbar.

That assumption is false when:

  • validation or file persistence fails;
  • settings.toml is unwritable;
  • the resulting override table is unchanged, where commitOverrideTable() returns without loadAll().

The taskbar then retains transient drag state until an unrelated rebuild; later layouts can move the spacer without
moving the excluded tile. Restore a valid scene independently of persistence, or safely handle the deferred result and
explicitly recover on failure/no change.

2. [P1] Spacer changes violate the UI phase and layer boundaries

taskbar_widget.cpp:430-448

beginDragVisual(), endDragVisual(), and syncDragSpacer() change layout participation and insert/remove children directly from pointer callbacks. Structural scene changes belong in Layout; callbacks should update drag state and call
requestLayout().

The spacer is also created with ui::node() and stored as Node*. Shell code may use controls only; InputArea is the sole
raw-node exception. Use an existing control such as Box for the spacer and create/reposition/remove it during
doLayout().

3. [P2] Releasing after the hold delay still launches the application

taskbar_widget.cpp:913-927

After 300 ms, the timer sets m_drag.armed, but release only checks m_drag.active. A hold followed by release without
motion therefore falls through to onClick() and activates or launches the pinned app.

That contradicts the described hold-to-drag behavior and differs from the existing dock implementation, which consumes an armed gesture on release. Treat armed as consumed, set click suppression, and cancel the drag without committing.

4. [P2] The movement threshold ignores interface scale

taskbar_widget.cpp:942

kDragArmDistance is a fixed 8.0F, while task geometry scales with m_contentScale. At 2× interface scale, the effective
tolerance is half as large relative to the tile, making normal click jitter more likely to arm a drag.

The canonical threshold already exists as Style::dragStartThreshold in logical pixels. Compare against
Style::dragStartThreshold * m_contentScale.

@ItsLemmy

Copy link
Copy Markdown
Collaborator

Mark it ready for review, when done, thx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants