Tug class
A glass surface on a short tether - a weight on a rubber leash, not a soap bubble.
The model, all lengths as ratios of the surface's shorter side so the gesture feels identical at any size:
- The pull is measured from the surface's own CENTER, not from where the finger landed - grabbing an edge and sliding inward is still a pull, and re-grabbing a returning surface needs no bookkeeping. The offset FOLLOWS the finger on a critically damped chase spring, integrated frame by frame on the widget's own ticker (the comet pattern): pointer events only move the TARGET. Retargeting a controller per event starves the simulation - a high-frequency mouse restarts it before it ever ticks and the surface freezes; and without any follow spring a grab near the edge would teleport straight to full extension. At rest under a motionless finger the tick snaps to the target once and stops writing - a held button costs nothing per frame.
- Inside a dead zone (dead, 0.24 of the side) nothing moves: a press that is only a press cannot shiver, and breaking free gives the pull a beginning.
- Past it the offset is
cap * tanh((d - dead) / cap)along the same vector, with cap only 0.16 of the side. Asymptotic - near 1:1 while small, never a wall. (Deliberately tanh and not the package's morphRubberband: tanh saturates noticeably faster, and this exact hand feel is the one that was tuned by eye.) Travel and stretch trade against each other, and the balance here leans hard toward shape: the surface is being stretched, not relocated. - The deformation locks to the SCREEN axes - no ellipse rotated
into the drag. Each axis grows on its own displacement, squared
(
1 + stretch * e^2), and neither axis ever squashes: the surface moves as a whole and stretches a little, it does not get pressed into a lens. The growth extends TOWARD the pull: the reported offset carries half of each axis's growth (continuous -e * |e|, no sign flip at zero), so the trailing edge stays planted while the leading edge reaches for the finger. The offset is clamped as a VECTOR before it splits into axes - per-axis clamping makes the area breathe on a circular sweep. A small lean rides inside the scale. - The finger COMPRESSES glass: pointer-down sinks the surface to press on an overshoot-free spring (popping back out past rest is the single most bubble-like thing a button can do).
- Release hands the chase velocity to the return springs, so a flick lands with its momentum. Grabbing mid-return seeds the chase from the return's own velocity - continuity in both directions.
Two ways to consume it:
- Default: the pull paints as a render transform above child. A MorphTag inside rides along, so a flight launches from wherever the surface stands.
- Channel mode (channel set): the pull is written straight into a MorphPieceChannel, so the skin moves REAL mass geometry - a MorphPiece inside a MorphSkin. The mass itself then carries the tether: pull one piece toward another and the skin necks them into one body, which no paint transform can do. The skin paints the content with the full channel transform (one rigid body); Tug corrects it from inside so the content rides the tether, sinks with the press in full, and follows only follow of the surface stretch - glass deforms what is printed on it, a little.
- Inheritance
Constructors
Properties
- cap → double
-
Tether cap as a ratio of the shorter side - the asymptote the
travel approaches past the dead zone.
final
- channel → MorphPieceChannel?
-
When set, Tug writes the pull into this channel and the skin
moves the real mass geometry; the widget itself paints only the
content correction. Fixed for the widget's lifetime: toggling it
swaps the tree shape. The channel's value is not reset on
unmount - the owner of the channel owns its value.
final
- child → Widget
-
The tuggable surface.
final
- dead → double
-
Dead-zone radius as a ratio of the shorter side.
final
- follow → double
-
How much of the surface stretch the content rides in channel mode
(the press sink always applies in full). Zero keeps the content
rigid; one glues it to the glass.
final
- hashCode → int
-
The hash code for this object.
no setterinherited
- key → Key?
-
Controls how one widget replaces another widget in the tree.
finalinherited
- lean → double
-
Peak lean in degrees, signed by the horizontal pull.
final
- motion → MorphMotion?
-
Profile whose closeMotion plays the return; defaults to
MorphMotion.normal.
final
- press → double
-
Scale while the pointer is down: glass compresses, never pops.
final
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
- stretch → double
-
Growth at a full pull as a ratio of the SHORTER side, eased in
quadratically. Both axes gain the same absolute amount, so a wide
pill stretches no further than a tall one. A sideways pull widens
only; a diagonal grows both by half.
final
Methods
-
createElement(
) → StatefulElement -
Creates a StatefulElement to manage this widget's location in the tree.
inherited
-
createState(
) → State< Tug> -
Creates the mutable state for this widget at a given location in the tree.
override
-
debugDescribeChildren(
) → List< DiagnosticsNode> -
Returns a list of DiagnosticsNode objects describing this node's
children.
inherited
-
debugFillProperties(
DiagnosticPropertiesBuilder properties) → void -
Add additional properties associated with the node.
inherited
-
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
toDiagnosticsNode(
{String? name, DiagnosticsTreeStyle? style}) → DiagnosticsNode -
Returns a debug representation of the object that is used by debugging
tools and by DiagnosticsNode.toStringDeep.
inherited
-
toString(
{DiagnosticLevel minLevel = DiagnosticLevel.info}) → String -
A string representation of this object.
inherited
-
toStringDeep(
{String prefixLineOne = '', String? prefixOtherLines, DiagnosticLevel minLevel = DiagnosticLevel.debug, int wrapWidth = 65}) → String -
Returns a string representation of this node and its descendants.
inherited
-
toStringShallow(
{String joiner = ', ', DiagnosticLevel minLevel = DiagnosticLevel.debug}) → String -
Returns a one-line detailed description of the object.
inherited
-
toStringShort(
) → String -
A short, textual description of this widget.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited