Motion runtime (
AnimatedValue<T>driven by curves / springs / easings) and shader-transition runtime (per-event GPU effect selection), plus the JSON-backedProfiletrees that configure both.
Per-output clock, typed AnimatedValue interpolating toward a target, polymorphic curves, and event-keyed profiles that map names like window.open and editor.snapIn to motion configs.
The library ships two parallel runtimes:
PhosphorAnimation) — AnimatedValue<T>, polymorphic Curves, IMotionClock, retarget / snap policy, stagger, and the Profile / ProfileTree / PhosphorProfileRegistry system.PhosphorAnimationShaders) — AnimationShaderRegistry discovers transition shader packs from search paths; the parallel ShaderProfile / ShaderProfileTree maps the same event names to an effect plus parameter values.Both profile trees use std::optional fields so a leaf can inherit (nullopt) or override (engaged) anything its parent declares. They share the dot-path event namespace but resolve through separate trees, so a user can change the curve without touching the visual effect.
SurfaceAnimator implements `phosphor-layer`'s ISurfaceAnimator and, for a given event, asks both registries what to play.
User-edited curves and profiles hot-reload from the user data dir through `phosphor-fsloader`.
| Type | Purpose |
|---|---|
PhosphorAnimation::AnimatedValue<T> | Typed in-flight animation (rect, point, color, scalar). Pull-model: consumer reads value() during paint. |
PhosphorAnimation::MotionSpec<T> | Runtime call-site bundle: profile, clock, retarget policy, callbacks. |
PhosphorAnimation::IMotionClock | Pull-model clock contract; one per output for mixed refresh rates |
PhosphorAnimation::QtQuickClock | QQuickWindow-bound IMotionClock (gated on PHOSPHOR_ANIMATION_QUICK=ON) |
PhosphorAnimation::Curve | Polymorphic base; every curve family implements step() advancing a CurveState |
PhosphorAnimation::Easing | Cubic-Bézier curve family (ease-out, ease-in-out, etc.) |
PhosphorAnimation::Spring | Critically-damped spring with configurable tension and friction |
PhosphorAnimation::CurveRegistry | Name-to-curve factory; lets profiles reference curves by string |
PhosphorAnimation::Profile | Serialisable bundle of curve + duration + stagger; optional fields support inherit/override |
PhosphorAnimation::ProfileTree | Hierarchical profile lookup with inheritance (window.open inherits from window) |
PhosphorAnimation::PhosphorProfileRegistry | Process-wide registry that hot-reloads profiles and emits live updates |
PhosphorAnimation::ProfileLoader | Sink for `phosphor-fsloader` |
PhosphorAnimation::CurveLoader | Sink for `phosphor-fsloader` |
PhosphorAnimation::RetargetPolicy | Mid-flight retarget behaviour: PreserveVelocity / ResetVelocity / PreservePosition |
PhosphorAnimation::SnapPolicy | Free helpers deciding whether a transition merits an animation |
PhosphorAnimation::StaggerTimer | Schedules animation starts across a group of windows |
PhosphorAnimation::SurfaceAnimator | ISurfaceAnimator impl that wires both runtimes into `phosphor-layer` |
| Type | Purpose |
|---|---|
PhosphorAnimationShaders::AnimationShaderEffect | Metadata for one transition (id, shader paths, parameter declarations) |
PhosphorAnimationShaders::AnimationShaderRegistry | Discovers transition shader packs from search paths; metadata-pack scan strategy from `phosphor-fsloader` |
PhosphorAnimationShaders::ShaderProfile | Per-event shader effect selection + parameter values |
PhosphorAnimationShaders::ShaderProfileTree | Hierarchical lookup matching ProfileTree's shape |
Animate a window rect with a profile:
#include <PhosphorAnimation/AnimatedValue.h>
#include <PhosphorAnimation/QtQuickClock.h>
#include <PhosphorAnimation/PhosphorProfileRegistry.h>
using namespace PhosphorAnimation;
QtQuickClock clock(window);
PhosphorProfileRegistry registry;
registry.addSearchPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
registry.refresh();
AnimatedValue<QRectF> geometry(initialRect);
MotionSpec<QRectF> spec{
.profile = registry.profileFor(QStringLiteral("editor.snapIn")),
.clock = &clock,
.retargetPolicy = RetargetPolicy::PreserveVelocity,
};
geometry.start(targetRect, spec);
// In paint:
QRectF current = geometry.value();Pick a transition via the shader runtime:
#include <PhosphorAnimation/AnimationShaderRegistry.h>
#include <PhosphorAnimation/ShaderProfileTree.h>
using namespace PhosphorAnimationShaders;
AnimationShaderRegistry shaders;
shaders.addSearchPath(systemDir);
shaders.addSearchPath(userDir);
shaders.refresh();
ShaderProfile sp = shaderProfileTree.profileFor(QStringLiteral("window.open"));
QString effectId = sp.effectId.value_or(QStringLiteral("dissolve"));
QVariantMap params = sp.parameters.value_or(QVariantMap{});AnimatedValue<T>::value() reads IMotionClock::now() inside the consumer's paint cycle. No timers; no per-frame Qt signals. One clock per output keeps mixed refresh rates honest.Curve returns enough state for the consumer to act on (current value, velocity, settled flag). The AnimatedValue doesn't know or care whether the curve is a spring, an ease, or a user-defined Bézier.window.open selects a motion profile and a transition effect, but through two independently-resolved trees, so a user can change the curve without touching the visual effect (or vice versa).optional means inherit.** ProfileTree and ShaderProfileTree both treat nullopt fields as "look at the parent." Engaged fields win. This makes overrides surgical — set one parameter, keep the rest of the bundle.PHOSPHOR_ANIMATION_QUICK gates Qt Quick deps.** Headless tools and the KWin compositor adapter don't link Qt6::Quick because they don't build QtQuickClock. Core motion has only QtCore + QtGui.QtCore, QtGui, QtQuick, QtQuickPrivate, QtQmlCustomParamsKey.hISurfaceAnimator (implemented by SurfaceAnimator)MetadataPackRegistryBase powers the AnimationShaderRegistry; profile / curve files come through its DirectoryLoader.