Motion
Overview
Section titled “Overview”Navius.Wpf.Motion is a standalone package (Navius.Wpf.Motion.csproj, targeting
net8.0-windows and net10.0-windows) carrying the pure C# SpringSolver over from the web
Navius Motion engine unchanged, plus a small WPF-side execution surface for driving values with
it: a keyframe baker for the zero-interruption tier and a per-frame ticker for the
interruptible tier. It has no dependency on Navius.Wpf.Primitives; nothing in that project
references it.
Public API surface
Section titled “Public API surface”Spring(readonly record struct): a spring definition. Construct it from raw physics parameters (Spring.Physics(stiffness, damping, mass, initialVelocity)), from a settle duration and bounce (Spring.FromDuration(durationSeconds, bounce, initialVelocity)), or from a perceptual visual duration and bounce (Spring.FromVisualDuration(visualDurationSeconds, bounce, initialVelocity)). Four named presets ship as static properties:Spring.Default(visual duration 0.3s, bounce 0.2),Spring.Smooth(0.35s, bounce 0, no overshoot),Spring.Snappy(0.2s, bounce 0.1), andSpring.Bouncy(0.35s, bounce 0.45, visible overshoot).WithInitialVelocity(velocity)returns a copy with a different initial velocity, used when carrying momentum into a retarget.SpringSolver: a stateless closed-form evaluator for one spring run from an origin to a target. Construct it withnew SpringSolver(spring, origin, target), then callPosition(t)andVelocity(t)(seconds in, value units and value units per second out) to sample the run at any time without stepping.IsAtRest(position, velocity)reports whether a sample has settled, andSettleDurationreports the real settle time in seconds (the resolved duration for a duration-built spring, or the time it takes to step to rest in 10ms increments, capped at 20 seconds, for a physics or visual-duration spring).SpringKeyframeBaker: a static class with one method,Bake(spring, from, to, initialVelocity), returning aSystem.Windows.Media.Animation.DoubleAnimationUsingKeyFrames. This is the zero-interruption tier: the first keyframe sits att = 0with thefromvalue, the last lands exactly ontoat the solver’s settle duration, and sample spacing is adaptive (as dense as roughly 4ms while the spring moves fast, as sparse as roughly 66ms once it settles into its tail). Feed the result straight intoBeginAnimation.SpringTicker(IDisposable): the interruptible runtime tier, driven byCompositionTarget.Rendering.new SpringTicker(spring, from, to, onValue)starts running immediately and callsonValueonce synchronously at construction, then once per frame with the current position.Retarget(newTarget)carries the current position and velocity into a new solve so motion stays continuous instead of snapping back to rest first.Value,Velocity,Target, andIsAtRestread the current state;Stop()detaches the rendering hook andDispose()callsStop().MotionPreset/MotionPresets: a data table of nine named enter/exit presets (Fade,FadeUp,FadeDown,Zoom,Pop,SlideUp,SlideDown,SlideLeft,SlideRight), each pairing a hiddenMotionVisualState(opacity plus an optional transform) with an enter spring and an exit spring.MotionPresets.Get(Preset)resolves thePresetenum to its definition. Every preset fades on both directions, exits withSpring.Snappy, and enters with a preset- specific spring (Smoothfor the plain fades,Defaultfor zoom and the slides,Bouncyfor pop).MicroPreset/MicroPresets: a data table of four named micro-interaction presets (Shake,Pulse,Shimmer,FocusGlow), each a keyframe list plus duration, easing, loop flag, and a reduced-motion behavior (MicroReduce.CollapseorMicroReduce.OpacityOnly).
The MotionPreset/MotionPresets/MicroPreset/MicroPresets/Preset types are carried over
as the same data shapes that feed the web repo’s CSS generator and WAAPI runtime, so the preset
table itself stays in agreement across platforms. Nothing in this package or in
Navius.Wpf.Primitives currently consumes them to drive a WPF animation; they exist as ported
data, not as a wired preset-to-animation pipeline on this platform yet.
Spring parameterization
Section titled “Spring parameterization”A spring is defined either physically (stiffness, damping, mass, initial velocity) or
perceptually (a duration or visual duration plus a bounce amount in [0, 1], where 0 is
critically damped and 1 is maximum overshoot). FromDuration solves for the undamped
frequency with a Newton-Raphson root find over the amplitude envelope so the requested settle
duration is honored; FromVisualDuration uses a direct formula instead
(stiffness = (2 * pi / (visualDuration * 1.2))^2) for the perceived duration, whose real
settle time runs longer than the perceptual one. SpringSolver reads Spring.DampingRatio
(damping / (2 * sqrt(stiffness * mass))) to pick among the three closed-form regimes:
underdamped (< 1, oscillates), critically damped (== 1), and overdamped (> 1).
Who uses it
Section titled “Who uses it”No primitive control in Navius.Wpf.Primitives references Navius.Wpf.Motion. The Toast family
is explicit about this in its own source comment: NaviusToastViewport “must not reference
Navius.Wpf.Motion, so there is no spring/keyframe engine involved” and instead drives its
150ms enter/exit with a plain DoubleAnimation. The Dialog family (and the Drawer family
alongside it, per the Dialog manifest’s web-deltas) is the same: both use a fixed 150ms
DoubleAnimation opacity fade and deliberately do not reference this package. The only consumer
in the repository today is apps/Navius.Wpf.Gallery/Pages/GatePage.xaml.cs, a gallery demo page
that opens a Popup and animates it in with SpringKeyframeBaker.Bake(Spring.Default, from: 10, to: 0) on a TranslateTransform.
This is the pattern GatePage.xaml.cs uses to slide and fade a popup into place with a spring,
verified against its actual source:
using Navius.Wpf.Motion;
var translate = new TranslateTransform(0, 10);PopupRoot.RenderTransform = translate;translate.BeginAnimation( TranslateTransform.YProperty, SpringKeyframeBaker.Bake(Spring.Default, from: 10, to: 0));PopupRoot.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(150)));For an interruptible drag or hover-retarget instead of a one-shot open animation, replace the
baker call with a SpringTicker:
var ticker = new SpringTicker(Spring.Default, from: 10, to: 0, value => translate.Y = value);// later, mid-flight, without snapping back to rest first:ticker.Retarget(newTarget: 40);Test coverage
Section titled “Test coverage”The test project tests/Navius.Wpf.Motion.Tests carries 29 test methods across five files:
SpringTests.cs, SpringSolverTests.cs, SpringTickerTests.cs, SpringKeyframeBakerTests.cs,
and MicroPresetDataTests.cs. The keyframe baker and ticker tests run on the STA thread
([StaFact]), since they exercise real WPF animation and dispatcher types. MicroPresetDataTests
covers only the ported preset data table itself; the web repo’s equivalent test file also covers
a WAAPI runtime (Motion.Shake()/MotionPrograms.Micro) that has no WPF counterpart and was not
ported.