Skip to content

feat(labels): pinned apps can show their name only while running - #63

Merged
thepathless merged 31 commits into
thepathless:experimentalfrom
priard:feat/pinned-labels
Oct 12, 2026
Merged

thepathless merged 31 commits into
thepathless:experimentalfrom
priard:feat/pinned-labels

Conversation

@priard

@priard priard commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

With always-on labels, a row of pinned apps spends most of its width on the names of apps that are not open. A closed app's name says little that the icon doesn't, and the open ones get lost among them.

What it does

A new setting, Labels → Pinned apps: Always / When running. It shows only with always-on labels that cover apps (Show on: All or Apps). It is stored as labelPinned, defaults to always (the dock looks as it does today), and travels with presets like the other label looks.

With When running:

  • A pinned app without a window shows only its icon. In a plate row it keeps a plate the size of an unlabelled tile, like the Omarchy button, so the row still reads as one set of buttons. Hovering it shows the usual tooltip with the name.
  • When the app starts, the icon bounces as before. The name waits until the bounce has landed: the hop that is in the air when the window appears comes down first, then the plate widens to take the name.
  • The widening follows the Animations level: Full eases the width, Reduced snaps it and fades the name in, Off is instant.
  • Closing the last window folds the name back.
  • With side indicators on plates, a folded tile keeps a column for the faint "playing without a window" mark (before or after the icon, as set); window marks wait for the name to open.

App groups, folders and running apps that are not pinned keep their names.

Notes

  • With alwaysRunToEnd: true, an animation's running turns false (and runningChanged fires) as soon as its binding does, although the hop keeps going to the end of the loop. stopped/finished only fire after the trailing pause. So the tile cannot ask the bounce whether it is still in the air. When the launch ends it reads bounceY < 0 instead, and a ScriptAction after the fall clears that, so the name opens the moment the icon lands rather than after the pause. I measured this with an offscreen qml probe.
  • DockLabels.nameFolded(mode, pinnedMode, pinned, awake) holds the rule, so it is covered by the node tests.

Depends on #59

This uses the Animations levels (Dock.motion), so the branch is cut from feat/animations and its diff includes #59 until that merges. The commits of this PR are the last two: "feat(labels): pinned apps can show their name only while running" and "fix(labels): a folded pinned name keeps its background media mark". I'll rebase onto experimental once #59 is in.

Screenshots

Pinned apps: Always (today's look):

Always

Pinned apps: When running (Chromium, Discord and Flea are closed):

When running

Testing

  • tests/unit/labels.test.mjs: labelPinned read and preset handling, nameFolded cases. dockmodel and shippedpresets tests cover the new look key.
  • CI-equivalent checks pass locally, including structure-check.py (DockModel.js stays at its cap).
  • Smoke test clean after a shell restart; screenshots above from the live dock.
  • Tested by hand: launching pinned apps with the bounce on, the name opening after the landing, folding on close, the tooltip on a folded tile.

priard added 30 commits October 11, 2026 10:18
Durations had drifted across the dock (120 ms linear next to 120 ms
OutCubic, 140, 150, 160, 180) and a setting that slows or stops motion
needs a single switch. DockMotion.plan(level) gives the timings and flags
for Full, Reduced and Off; the next commits make the dock read it.
It only edits state, like the slot logic beside it; moving it frees the
root's lines for the animation setting that follows.
Motion & Effects gets one switch for how the dock moves. The value is a
behaviour preference, so it lives in omadock.json but not in presets;
Dock.motion carries the plan the components read.
A key dropped from a section stays in the model, marked leaving, for the
length of its exit, and keeps its place; a key that comes back in that
time revives instead of being rebuilt. Each row also carries its slot
among the live rows, which label slots and home centres keep using while
something leaves. Row keys for tiles, folders, buttons and drives come
from the same module.
The model now applies DockKeys rows: removals wait out exitMs (the
motion plan's base duration, 0 at Off) and every row carries its live
slot. The delegates pick the roles up in the next commits.
Folders, command buttons, drives and their dividers move verbatim into
DockRightGroup, which gets the card, its wrapper and the outer row
passed in. DockCard was at its 800-line cap and the row animations that
follow need room; the probe's item geometry is identical before and
after.
…the row

Each element of the pinned run, the running run and the Omarchy button
sits in a RowSlot, which narrows and widens with the element and scales
and fades it (Reduced: fade only, Off: at once). The model keeps a closed
app's row for its exit, so the card and the neighbours ease along
instead of snapping; label slots and home centres use the live slot, and
the drag logic and itemGeometry measure live rows only (liveSlots). A
fully tiled app leaves the running run the same way.
A delegate's model roles reach it after it completes and without a
change signal, and a required property that shares a name with one of
RowSlot's own made a second property RowSlot never read, so nothing
animated out. Delegates now mark RowSlot's own leaving and fresh as
required; the slot draws as nothing for one turn, then grows if it is
fresh. Rows that arrive while the dock starts (the first 1.5 s of the
model) are not fresh, so startup does not animate in.
Minimized tiles, the folder/button run and the drives were plain arrays
rebuilt on every change; they move to KeyedListModel (DockKeys.tileKey,
folderKey, driveKey) inside RowSlots like the app runs. Every keyed
delegate now takes its data from the lookup only while the key is there
(a Binding read the gap first, so a leaving folder lost its name and
narrowed from the wrong width), and the trailing drop gap and ghost
count live folders.
The four divider slots become one RowDivider: its width eases to nothing
and its line fades when its section goes, and it stays in the row while
it has width, so the split panel after it narrows with the items. The
right group stays visible while its rows are on their way out. The
divider sets its direction before its width changes, so the leaving
curve matches the items'.
The queue bookkeeping moves beside dropParkSlot and gets the dock's
timer passed in; the root keeps the Timer and a one-line delegator,
which frees its lines for the popup lives that follow.
The context menu, the folder stack and the opened group were destroyed
the moment they closed, and their opacity Behaviors never ran. Each now
has a PopupLife in the dock that keeps its window until the fade ends;
the window fades and rises in, and on close shows the body's last frame
(a ShaderEffectSource kept current while open), since the popup's own
state clears at once. No input and no focus grab while it fades out.
A window mark popped in at full size and vanished on the spot, and the
badge flipped visible. A mark that goes now stays one exit long while it
shrinks and fades, a new one grows from nothing, and the badge scales
and fades in and out with the last count shown while it fades; its width
eases when the count gains a digit.
Behaviors and transitions read Dock.motion (through each component's
root, or through hoverFx for HoverFx and DockIconArt): the magnifier and
press dip keep their 110 / 90 ms, fades and colours take fast, layout
and hover levels base, the reveal and the autohide slide slow, with
OutCubic in place of the stray linear and OutQuad fades. Autohide slides
only at Full and fades at Full and Reduced. A static test lists any
literal duration left; the bounce, pulse, halo, ripple, glitch and label
typing keep their own timings and are listed as such.
The launch and urgent bounce, the pulse, the drop-target halo, the glow
ripple, the glitch burst and the label typing and scrambling run only at
Full. The bounce and the pulse now finish the hop or breath in flight
before they rest (alwaysRunToEnd), so a window appearing mid-bounce no
longer snaps the icon down from 13 px.
It draws its line in Color.bar.text, so like the other files that use
Color it needs the versioned import (Qt 6.12's own Color singleton).
The folder icons moved out with the right group (DockRightGroup), which
imports it itself.
The menu, the folder stack and the opened group size themselves to 0
the moment their state clears, and a ShaderEffectSource whose source is
0x0 draws nothing: the fade-out ran on an empty window. While closing,
the body keeps its last size.
Two identical command buttons are one button (DockFolderLogic); in the
keyed model they made two rows, and unpinning one left the second row in
place for good.
A leaving row shared its slot with the live row after it, so a drag
opened two drop gaps (one in the leaving item), a file drag found the
leaving folder's gap, and a row that came back before its exit ended had
lost its label width entry to its neighbour for good, skewing home
centres and plate edges. Leaving rows now report label slot -1 and take
no drop line or folder gap; reviving re-registers the label.
The slide was gated on layout motion, which Reduced turns off, so the
card left in one frame while its opacity faded out of view. At Reduced
the card now moves off screen once the fade ends, and back before it
fades in.
The dividers and the Omarchy button follow data that arrives after the
dock is built (the pinned list, the first window list, the config), so
every shell start grew the dividers in and a hidden Omarchy button
narrowed out. Changes in their first 1.5 s land at once; the divider's
width is set by hand after its direction, like RowSlot's appear.
An easing bound to the same flag as the animated value (want, count,
wanted, going, dockVisible) could start with the old easing: on the way
out the popups, badges, row items, window marks and the autohide fade
ran OutCubic instead of InCubic. The Reduced autohide pause had the same
race, so the card could leave the screen before its fade. Each Behavior's
targetValue is set before its animation starts, so the easing and the
pause read the direction from it.
PopupLife ends a popup's life when its level gets back to 0. Opened and
closed in one turn, the level never left 0, no change came, and alive
stayed true: the popup's window (its LazyLoader) lived on, invisible.
Closing now ends the life at once when the level is still at 0.
…rinks

Every plan change restarted the timer that removes a going mark, so with
frequent changes (a window title, the focus) the mark lingered past its
exit. The timer now starts once per exit. The mark's cell kept its full
size while the mark shrank and faded, and the others jumped when it was
removed; along the row the cell now grows and shrinks with its mark (Full
only), with the mark centred in it.
During its 180 ms exit a leaving app, folder or drive could still be
clicked, hovered or dropped on. RowSlot disables its content while it is
not wanted.
RowDivider stays visible while it has width, so a split panel narrows
instead of vanishing. inPinZone and overRunningAt read that visibility,
so for the length of the exit a going divider still decided where the
pin zone and the running apps started. They now also check that it is
shown.
Reduced keeps fades and drops movement, but tooltips still lifted 6 px
as they faded in. TooltipLife exposes the motion plan's rise flag and
TooltipWindow lifts only with it.
It read only Dock.qml and components/*.qml, so a literal duration in
components/logic or components/settings went unseen. It now scans every
QML file outside tests, allows the settings search-hit highlight (not
the dock), and fails on an allowance that no longer matches a line.
With always-on labels a row of pinned apps spends most of its width on
names of apps that are not open. labelPinned "running" (Settings ->
Labels -> Pinned apps, shown for always-on labels on apps) folds a
pinned app's name away while it has no window: the icon stays, on its
own plate in a plate row, and a hover shows the tooltip with the name.

When the app starts, the name waits for the launch bounce: the hop in
the air when the window appears lands first (the animation's running
turns false at once under alwaysRunToEnd, so the tile reads the icon's
height and a ScriptAction after the fall), then the plate widens. Full
eases the width, Reduced snaps it and fades the name, Off is instant.
Closing the last window folds it again.
priard added a commit to priard/omadock that referenced this pull request Oct 11, 2026
With side indicators on plates, a pinned app folded by "When running"
lost its faint "playing without a window" mark: the column's room and
opacity scaled with the name's progress, which is 0 while folded, and
the mark under the icon is hidden whenever side marks are on.

A foldable name now keeps the column's room and opacity on their own:
before the icon as the usual lead, after it as a tail that hands over to
the name's trail as the name opens. While folded the column holds only
the background mark; window marks still wait for the bounce to land.
@thepathless
thepathless merged commit 7658662 into thepathless:experimental Oct 12, 2026
5 checks passed
@thepathless

Copy link
Copy Markdown
Owner

🎉 Thank you so much for this contribution, @priard! Really appreciate the care you put into it. ✨ Verified on experimental — static gates, unit suites (239 node + 116 python) and the full live dock battery all green, including the marks-centering suite — and merged. Your work is credited in the changelog. 🙏🚀

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.

2 participants