Documentation
¶
Overview ¶
Package spring provides physics-based motion for animating a scalar value toward a target. It is the Pulse layer's bridge to the traer particle system: every Spring owns a tiny traer.ParticleSystem containing one fixed anchor (at the target) and one free particle (whose 1D position is the animated value), connected by a damped linear spring.
Reach for spring when motion needs to feel physical: button presses, list reveals, focus rings, drag inertia. For non-physical motion — fades, slides, simple colour interpolation — pulse/tween is cheaper and more predictable.
Defer-scoped allocation ¶
A Spring is stateful. It must be allocated once per subscription and kept alive across emissions and frames; reconstructing it inside a per-emission map function or a per-frame layout function would reset the simulation every render. The canonical pattern is to allocate inside an [rx.Defer] closure (DESIGN §"The rx.Defer Subscription-State Pattern"):
rx.Defer(func() rx.Observable[layout.Widget] {
sp := spring.New(0, 0, spring.Options{Stiffness: 20, Damping: 8.94})
return rx.Map(targets, func(target float64) layout.Widget {
sp.SetTarget(target)
return func(gtx layout.Context) layout.Dimensions {
sp.Tick(math.Max(1, fps.Value/30))
// ... render using sp.Value() ...
if !sp.Settled(0.005) {
gtx.Execute(op.InvalidateCmd{})
}
return layout.Dimensions{}
}
})
})
Time step ¶
Spring.Tick takes invDt — the inverse of the simulation step, matching traer.ParticleSystem.Tick. The DESIGN-recommended frame loop convention is max(1, fps/30): floor the step at 1 (ensuring a dt no larger than 1s under starvation) and otherwise pass the instantaneous frame rate divided by 30 (the original Traer Physics reference rate). This trades real-time accuracy for Verlet stability when the host stutters.
Index ¶
Constants ¶
const ( DefaultStiffness = 0.4 DefaultDamping = 0.7 DefaultMass = 1.0 )
Default parameters approximate a moderately stiff, lightly underdamped spring — visible motion with one small overshoot before settling. Callers tuning for a specific feel should override all three.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Options ¶
type Options struct {
// Stiffness is the spring constant k in the textbook damped-spring
// model m·ẍ = −k·x − c·ẋ. Higher values produce faster oscillation
// and a stiffer pull toward the target.
Stiffness float64
// Damping is the linear damping coefficient c. Critical damping
// (no overshoot, fastest settle without ringing) is c = 2·√(k·m).
// Below critical, the spring oscillates; above, it creeps.
Damping float64
// Mass of the free particle (m). Higher mass adds inertia,
// slowing the response without changing the underlying frequency
// ratio.
Mass float64
}
Options configures a Spring at construction. Zero-valued fields are replaced with package defaults; pass explicit values for any field whose default does not match the desired feel.
type Spring ¶
type Spring struct {
// contains filtered or unexported fields
}
Spring animates a scalar value toward a target via a 1-D damped spring. The simulation is fully deterministic: identical construction arguments and identical Spring.Tick calls produce bit-identical trajectories.
func New ¶
New constructs a Spring whose value starts at start and is heading toward target. Zero-valued option fields are replaced with package defaults.
func (*Spring) SetTarget ¶
SetTarget changes the target without resetting the free particle's position or velocity, so the in-flight motion blends smoothly into the new trajectory.
func (*Spring) Settled ¶
Settled reports whether the spring is at rest within tolerance: the value is within tolerance of the target AND the velocity magnitude is within tolerance of zero. Both checks are required because a damped oscillator passes through the target at peak velocity on its way to settling.
func (*Spring) Tick ¶
Tick advances the simulation by 1/invDt seconds. It returns the activity metric reported by the underlying particle system — √(Σ |v|²) across all particles — which callers can use to decide whether to schedule another frame.