Skip to main content

Value Types and Widgets

Every keyframed value is blended by an interpolator registered for its type, and edited in the Inspector by a widget registered for its type. Both registries are open, and the built-in types use them the same way yours would.

Interpolators​

Flow blends number, Vector2, Vector3, CFrame, Color3, UDim, UDim2, NumberRange, and Rect. Every other type is stepped: a keyframe's value holds until the next one.

To make another type blend, register an interpolator for its typeof name. It receives the two keyframe values and how far between them to go:

local Flow = require(ReplicatedStorage.Flow)

Flow.Interpolation.Register("NumberSequence", function(a: NumberSequence, b: NumberSequence, alpha: number)
-- Assumes both sequences have the same keypoint times
local keypoints = {}
for index, keypointA in a.Keypoints do
local keypointB = b.Keypoints[index]
table.insert(keypoints, NumberSequenceKeypoint.new(
keypointA.Time,
keypointA.Value + (keypointB.Value - keypointA.Value) * alpha,
keypointA.Envelope + (keypointB.Envelope - keypointA.Envelope) * alpha
))
end
return NumberSequence.new(keypoints)
end)

Register it on every client that plays the sequence, for example from a LocalScript in StarterPlayerScripts, before the first play. A Property section animating a NumberSequence property, such as a beam's Transparency, then blends instead of stepping.

alpha usually runs from 0 to 1, but keyframe curves that overshoot, such as Back and Elastic eases or a Bezier handle outside the box, pass values beyond that range. Clamp inside your interpolator if your type can't extrapolate. The same interpolator is also what blending uses to mix overlapping sections.

The editor's preview runs on the plugin's own copy of the runtime, which doesn't run your game scripts, so a type you registered in game still steps in the preview.

Per-component curves​

A type made of several numbers can let each component take its own keyframe curve, so a Vector3 can ease on Y and stay linear on X. Flow.Interpolation.RegisterComponents(typeName, components, blend) registers that: components lists the component names in the order the Inspector's Component menu shows them, and blend(a, b, alphas) receives one alpha per component, keyed by name. The built-in types with components are Vector2, Vector3, Color3, UDim, UDim2, NumberRange, Rect, and CFrame, whose rotation is a single component. A type without a component blender plays a per-component curve as linear.

Inspector widgets​

The editor builds each parameter's Inspector row from the widget registered for its type: number fields, the color picker for Color3, the gizmo for CFrame, a dropdown for enums, and a plain text field for anything without one.

A custom section type can register widgets of its own by adding a Widgets table, keyed by type name. The editor registers them when it loads the module. Widgets are registered by type for the whole editor, so the example below changes how every NumberRange is edited, not just this section's.

return {
Name = "Spotlight",
Capability = "Sampled",
SubjectClass = "SpotLight",
Parameters = {
Range = { Type = "NumberRange", Default = NumberRange.new(8, 16), Keyframable = true },
},
Widgets = {
NumberRange = function(context)
return context.Components(
{ "Near", "Far" },
function(value) return { value.Min, value.Max } end,
function(numbers) return NumberRange.new(numbers[1], math.max(numbers[1], numbers[2])) end
)
end,
},
Evaluate = function(context, localTime)
return { Range = context:GetParameter("Range", localTime).Max }
end,
}

A widget builder receives a context and returns a handle with an optional SetValue(value), which the editor calls when the playhead moves. The context provides:

MemberMeaning
TypeThe resolved type name; Enum.<EnumType> for an enum
ValueThe current value
DeclarationThe parameter's declaration, if it has one
ParentThe frame to build the widget in; it is cleaned up with the Inspector row
Commit(value)Records a new value as one undo step
MaidFor connections and instances the widget creates outside Parent
Components(labels, read, write, decimals?)Builds a row of labeled number fields, the way the built-in Vector3 and UDim2 widgets do

If a builder errors, that parameter falls back to the plain text field rather than breaking the Inspector.