QML value-type wrapper around PhosphorAnimation::Profile.
Q_GADGET per Phase 4 decision O — compile-time snapshot shape for PhosphorMotionAnimation.profile: PhosphorProfile { … } (decision R's value-binding branch). The path-string branch goes through PhosphorProfileRegistry and lands in a later sub-commit.
Optional-field treatment in QML
Profile's fields are std::optional<T> so ProfileTree inheritance can distinguish "unset, inherit" from "explicitly set to library
default" (see Profile.h's class doc). QML cannot represent std::optional<T> directly, so the wrapper collapses the distinction at the QML boundary:
- Reading a property returns the effective value (
Profile:: effective*). Unset fields read back as their library default.
- Writing a property engages the optional when the value validates, so the field becomes "explicitly set". A write that FAILS validation disengages it instead, which is one of two QML-reachable ways to return a field to "unset" (from C++, call
Profile::*.reset() directly). presetName is the exception on this bullet too: its setter validates nothing and always engages, so QML has NO path back to "unset" for it, and reads cannot tell unset from engaged-empty (both return ""). A QML read-modify-write of a profile therefore converts an inherited presetName into an explicit empty override — acceptable because the field is a display label, but load-bearing on the C++ side where engaged-empty blocks tree inheritance (Profile::toJson emits it).
curve is the exception to BOTH bullets, because there is no Profile::effectiveCurve():
- Reading it when unset returns a NULL
PhosphorCurve, NOT the library default. isNull() is true and typeId is empty on a fresh gadget; the OutCubic default is substituted later, by withDefaults() and by consumers. Check isNull() rather than assuming a curve is there.
- Assigning a default-constructed
PhosphorCurve disengages it, which is the second way to reach "unset" from QML — and setCurve is also the one setter that applies no validation at all, since a curve handle is either a valid curve or null.
This matches how plugin authors typically use PhosphorProfile: construct a compile-time literal with every field they care about, hand it to PhosphorMotionAnimation.profile, and treat it as immutable. ProfileTree inheritance is a C++-side concern accessed through the core ProfileTree API, not through this wrapper.