widgets library
The opinionated widget layer: interesting, correct uses of the
morph engine, shipped as ready recipes - press springs, liquid
selection, glass tethers. Everything here is built strictly ON
package:morph/foundation.dart and motor; the engine never depends
on this layer, and taste knobs here are expected to move with the
owner's app.
This layer is about USING morph well, not about reproducing a platform's look: there is no glass shader here and none is planned - surface shading of that kind is the platform's business, not this package's.
Classes
- ChaseSpring
- A critically damped spring chasing a MOVING target, integrated step by step by its owner's clock (a Ticker's elapsed deltas).
- CupertinoMotion
- A collection of spring motions that are commonly used in Cupertino apps.
- CurvedMotion
- A motion based on a fixed duration and curve.
- MaterialSpringMotion
- Material Design 3 spring motion tokens for expressive design system.
- MorphAnchor
- The declarative layer on top of the engine: a morph as a function of state, in the spirit of SwiftUI's matchedGeometryEffect, adapted to Flutter's rules.
- MorphController
- A single retargetable scalar spring from which all derived morph values are computed. An interruption (open during close and vice versa) is a new SpringSimulation starting from the old one's current value and velocity: the velocity carry-over automatically makes the interruption consistent for every derived property.
- MorphFlight
- One flight: the tag -> shuttle -> target trio. Lives from launch to close finalization. A repeated showMorph on the same tag does not create a new flight - it retargets this one, which makes interruption free for the caller.
- MorphFlightScope
- Gives overlay content access to its flight (e.g. MorphReveal finds it here) without threading the flight through builder parameters.
- MorphFrame
- An immutable snapshot of all render values for one flight frame.
- MorphLink
- An explicit pipe between two pieces by id - for distant pieces where proximity fusion is not enough. width defaults to half the smaller height of the pair.
- MorphMass
- A raw SDF mass poured into a skin - contentless geometry fused into the contour alongside the pieces (MorphSkin.extraMasses). A mass has no morph identity: it cannot fly, it only adds to the shape. Sealed: MorphMass.box and MorphMass.bridge are the whole vocabulary.
- MorphMenuItem
- An entry of showMorphMenu.
- MorphMotion
- A morph's motion profile: a pair of Motions from the motor package plus closeVelocityHint. Character can only be changed as a whole profile (a mid-flight profile swap retargets the simulation); custom profiles are built through the public constructor from any Motions (Cupertino presets, Material tokens, curves, custom springs).
-
MorphPageRoute<
T> - The route half of showMorphRoute; usable directly with Navigator.push for named-route setups.
- MorphPiece
- A piece of a liquid group: explicit geometry (no tree measurement, like the rest of the morph system) plus live content on top of the skin. rect and radius are plain frame data: an animating consumer derives them from its own spring value.
- MorphPieceChannel
- A per-frame geometry channel for a MorphPiece: a translation plus per-axis scales over the piece's base MorphPiece.rect, written by the app (a drag, a tether, an orbit) and consumed by the skin's render object directly. A write repaints and re-traces the skin with no widget rebuild and no relayout - the same citizenship flight ticks have.
- MorphReveal
- Content choreography: a block unfolds on its own sub-range of the same spring's progress - fade, slide-in, scale, and a vertical "unsquash" from the anchor. Content does not just appear and "be"; it unpacks out of the morph's direction of growth.
- MorphScope
- The registry of tags and flights. Installed once near the top of the tree (not necessarily above the Navigator, but inside MaterialApp). A flight's shuttle renders in the NEAREST enclosing Overlay, so a nested navigator keeps its flights inside itself; in a single-navigator app that is the root overlay anyway. Also serves as the TickerProvider for all flights.
- MorphScopeState
- The registry behind MorphScope: tags by id, live flights by id, and the ticker provider all flights share.
- Shared elements inside a flight: a piece of content (an album cover, an avatar, an icon) that TRAVELS from its place in the source to its place in the target instead of riding the fade-through crossfade.
- MorphSkin
- A group of pieces with one shared "skin": the smooth union of their SDFs traced into a single Path behind live content. Nearby pieces fuse with a concave fillet on their own; the blend width comes from style or an explicit blend (low - a geometric joint, high - a gooey neck).
- MorphSkinStyle
- A named bundle of fusion knobs - in the spirit of MorphMotion presets. k and cell are distances in pixels: the presets are calibrated for button-scale UI (pieces of 40-150px); scenes at other scales should scale k along via an explicit MorphSkin.blend.
- MorphSurface
-
The Material adapter of the widget layer: renders the visible
surface of the nearest MorphTag FROM its declared spec
(Material + InkWell with the spec's shape), so the button and the
flight can never disagree about the surface. The tap handler
receives a context UNDER the tag, which is why showMorph* can omit
from:- the morph flies from the surface the finger is already on. - MorphSurfaceSpec
- The morph's surface model as a value: everything the engine can interpolate about a surface - (shape, color, elevation). Declared once at a MorphTag (via MorphTag.spec) and readable by any descendant through MorphTag.specOf, so the visible surface is BUILT FROM the declaration instead of duplicating it - drift between "what the button looks like" and "what the morph believes" becomes structurally impossible, whatever rendering stack draws it.
- MorphTag
- The identity marker: registers its RenderBox in the scope and hands the engine a (rect, shape) pair. The shape is declared, never inferred from decoration. Prefer declaring the whole surface once via spec and rendering from specOf; the individual fields remain for terse cases.
- MorphTagState
- The registration and flight-side machinery of a MorphTag.
- MorphTapTarget
-
The surface-less sibling of MorphSurface, for content inside a
MorphPiece: the skin IS the surface there ("one mass - one
shadow"), so this recipe adds only what the glass cannot - button
semantics, a click cursor and the tap, with the same
context-under-the-tag contract that lets showMorph* omit
from:. A Material here would paint a second outline that splits from the mass the moment anything deforms it. - MorphTargetSpec
- Description of a flight target: how to compute its rect from the current overlay size (re-evaluated every frame - the target is live; a window resize or the keyboard retargets it automatically) and what surface it has.
- MorphTheme
- Ambient engine defaults as a ThemeExtension: "how this app morphs", declared once instead of being threaded through every call site.
- Motion
- A motion pattern such as spring physics or duration-based curves.
- SpringButton
- Press physics on a real spring: pointer-down retargets the scale toward the pressed depth, release retargets back with bounce and velocity carry-over - interruptible mid-press like everything else in the system. No timelines, no curves.
- Tug
- A glass surface on a short tether - a weight on a rubber leash, not a soap bubble.
Enums
- MorphDirection
- Which end of the trajectory the spring currently targets.
- MorphPhase
- Semantic phases of a flight. Boundaries are computed from the clamped spring value; consumers react to the phase instead of comparing rawValue against magic numbers.
- How a shared pair blends during the flight.
Constants
- morphDragCommitDistance → const double
- Default commit threshold for MorphFlight.endDrag: releasing with a projected displacement farther than this (in px, any direction) commits the close flight. Distance-based and direction-agnostic: flinging the card anywhere reads as "throw it away".
- morphDragCommitVelocity → const double
- Default commit threshold on the release velocity (px/s): a hard fling commits even when the projected distance stays short.
Functions
-
computeMorphFrame(
{required double value, required Rect sourceRect, required Rect targetRect, required ShapeBorder sourceShape, required ShapeBorder targetShape, required Color sourceColor, required Color targetColor, required double maxScrimOpacity, double sourceElevation = 0, double targetElevation = 24}) → MorphFrame -
Computes every render value of a flight frame from one spring
value- the whole visual contract in a single pure function. -
maybeMorphAnchorRect(
BuildContext context) → Rect? - Like morphAnchorRect, but null when the context has no laid-out box - for callers that can fall back (a centered dialog instead of an anchored popover) rather than crash.
-
morphAnchorRect(
BuildContext context) → Rect -
The rect of
context's render box in the coordinate space of the enclosing Overlay - the space every flight, and therefore every MorphTargetSpec.rectFor, works in. Use it to capture popover anchors at tap time: a rawlocalToGlobalreturns SCREEN coordinates, which silently drift from overlay coordinates the moment the morph lives inside a nested navigator. -
morphBumpedRect(
Rect rect, {required double value, required Offset impactAxis, required double bumpScale, required double bumpRecoil}) → Rect - The same bump applied to geometry: the rect squashes and kicks around its own center. For consumers whose mass IS the visual (the liquid skin) a transform is not an option.
-
morphCloseHintScale(
double travelPx) → double - Scales closeVelocityHint by the pixel travel of the flight: value space normalizes distance, so without this multiplier a near and a far close would bounce identically. A far close lands heavier and springs more visibly.
-
morphDragArm(
double distance) → double - The arm ramp for the commit cue: 0 below morphDragCommitDistance, smoothstepping to 1 over the next 60 px. Past the threshold the card visibly "arms" - extra recede, thinner scrim, a slight lean toward home - so the hand knows a release will close. A pure, reversible function of the displacement: backing off disarms the same way.
-
morphDragRecede(
double distance, {double dimension = 400}) → double - How far the container has "receded into the hand" during a free drag: a smooth asymptotic fraction (0 at rest, approaching 1) of the displacement distance. Drives the subtle scale-down and the scrim dimming while dragging - both stay pure functions of the displacement, so interruption continuity holds by construction.
-
morphDragScale(
double recede, double arm) → double - The container scale of a live drag: a subtle recede into the hand, deepened by the arm cue. Shared by the shuttle and the settled route page for the same reason as morphDragScrimFactor.
-
morphDragScrimFactor(
double recede, double arm) → double - The scrim attenuation of a live drag: the factor the displacement multiplies into the scrim opacity - thinning with morphDragRecede and thinning further as morphDragArm rises. The one implementation of the composition, shared by the shuttle and the settled route page, so a drag dims identically on both sides of the second latch.
-
morphLandingBump(
{required double value, required Offset impactAxis, required double bumpScale, required double bumpRecoil}) → ({Offset kick, double scaleX, double scaleY}) - The landing bump: one set of formulas for every consumer (the MorphTag content transform and the liquid-skin rect deformation). Full-wave: it lives on the FULL spring value - a dip below zero gives squash along the impact axis, a slight stretch across it, and a recoil kick; the return into positive plays the reverse. A degenerate axis falls back to vertical.
-
morphProjectValue(
double value, double velocityPerSecond, {double decelerationRate = 0.998}) → double - Momentum projection (WWDC18 "Designing Fluid Interfaces"): where the value would coast to under natural deceleration. The "close or return" decision is made from the projection, not from the current position.
-
morphRubberband(
double x, {required double dimension, double coefficient = 0.55}) → double - The canonical overdrag resistance formula (rubber-banding): f(x) = xdc / (d + c*x). Monotonic, asymptotically approaches d, resistance is felt from the very first pixel (slope c at zero). c = 0.55 is the classic iOS coefficient.
-
showMorph(
BuildContext context, {Object? from, required MorphTargetSpec target, required MorphContentBuilder builder, MorphMotion? motion, bool barrierDismissible = true, double? maxScrimOpacity, Color? scrimColor, Color? shadowColor, VoidCallback? onDismissRequested, String? semanticLabel}) → MorphFlight - The public entry point: all the plumbing (Ticker, listener, Overlay, ghost, and the handoff latch) is under the hood. If a flight for the tag already exists, the call retargets it instead of creating a new one (RETARGET CONTRACT: the existing flight keeps its target, builder, barrier and scrim; only motion, dismissal routing and the semantic label are updated).
-
showMorphDialog(
BuildContext context, {Object? from, required MorphContentBuilder builder, double width = 440, double height = 360, MorphSurfaceSpec? surface, ShapeBorder? shape, Color? surfaceColor, MorphMotion? motion, bool barrierDismissible = true, double? maxScrimOpacity, Color? scrimColor, Color? shadowColor, VoidCallback? onDismissRequested, String? semanticLabel}) → MorphFlight - The dialog adopts its look from the app's DialogTheme (shape, color) unless overridden explicitly.
-
showMorphMenu(
BuildContext context, {Object? from, required List< MorphMenuItem> items, MorphSurfaceSpec? surface, MorphMotion? motion, double width = 250, double maxScrimOpacity = 0.2, String? semanticLabel}) → MorphFlight - The iOS 26 staple as one call: a control becomes ITS OWN menu.
-
showMorphRoute<
T> (BuildContext context, {Object? from, required MorphContentBuilder builder, MorphTargetSpec? target, MorphMotion? motion, bool barrierDismissible = true, double? maxScrimOpacity, Color? scrimColor, Color? shadowColor, String? semanticLabel}) → Future< T?> - A real Navigator route whose visual transition is a morph flight.
-
showMorphSheet(
BuildContext context, {Object? from, required MorphContentBuilder builder, double? height, double heightFactor = 0.5, double maxWidth = 560, MorphSurfaceSpec? surface, ShapeBorder? shape, Color? surfaceColor, MorphMotion? motion, bool barrierDismissible = true, double? maxScrimOpacity, Color? scrimColor, Color? shadowColor, VoidCallback? onDismissRequested, String? semanticLabel}) → MorphFlight - The sheet adopts its look from the app's BottomSheetTheme (shape, color) unless overridden explicitly: a morph sheet inside a foreign design system looks native with zero configuration.
-
uniformMorphRadius(
ShapeBorder shape, Size size) → double? - The single radius of a shape when it can be expressed by one (Circle, Stadium, rrect/superellipse with equal corners); null for exotic shapes.
Typedefs
- MorphContentBuilder = Widget Function(BuildContext context, MorphFlight flight)
- Builds the target content of a flight; called once per flight and reused between spring ticks.