Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 14x 14x 14x 14x 1x 1x 1x 1x 1x 1x 1x 1x 1x 100x 100x 100x 4x 100x 96x 96x 100x 100x 1x 1x 1x 1x 1x | // Animation registry — parallel to Constructions but distinct purpose.
//
// A Construction builds an element once at init() time. An Animation
// describes how to *reveal* that element when a slide transitions:
// lines trace from one endpoint to the other, circles sweep an arc,
// polygons trace edges in order then fade in the face. Multiple
// animations can target the same element type — e.g. a Line might
// be revealed via the shorthand A.Line.straightEdgeConnect (pencil
// stroke A → B) or the long-form A.Line.compassTransfer (compass
// walks a radius from another line then drops the straightedge).
// Authors pick the right one per slide.
//
// Names follow the same `Type.method` shape as Constructions
// (E.Line.connect ↔ A.Line.straightEdgeConnect). The enum value is
// what authors pass; the human-readable name string is used for the
// string-form fallback (param strings) + console.warn diagnostics.
import {GeomElement} from "./GeomElement";
import {Slate} from "../Slate";
// Typed enum, parallel to AllConstructions. Numeric backing — typo-
// safe at compile time and cheap to compare at runtime.
export enum AllAnimations {
INSTANT,
POINT_APPEAR,
POINT_INTERSECT, // future
POINT_SLIDE,
POINT_FOLLOW_PATH,
LINE_STRAIGHT_EDGE_CONNECT,
LINE_STRAIGHT_EDGE_EXTEND,
LINE_COMPASS_TRANSFER, // future
CIRCLE_COMPASS,
CIRCLE_COMPASS_EXPLICIT, // future
CIRCLE_COMPASS_TRANSFER,
POLYGON_OUTLINE,
POLYGON_OUTLINE_AND_FILL,
POLYGON_SUPERPOSE,
POLYGON_EQUILATERAL_BUILD,
SECTOR_SWEEP,
GROUP_CLONE_ASIDE,
}
// Typed constants object, mirroring E for constructions. Authors
// write `A.Circle.compass` instead of remembering the raw enum.
export const A = {
Point: {
appear: AllAnimations.POINT_APPEAR,
intersect: AllAnimations.POINT_INTERSECT, // future
slide: AllAnimations.POINT_SLIDE,
followPath: AllAnimations.POINT_FOLLOW_PATH,
},
Line: {
straightEdgeConnect: AllAnimations.LINE_STRAIGHT_EDGE_CONNECT,
straightEdgeExtend: AllAnimations.LINE_STRAIGHT_EDGE_EXTEND,
compassTransfer: AllAnimations.LINE_COMPASS_TRANSFER, // future
},
Circle: {
compass: AllAnimations.CIRCLE_COMPASS,
compassExplicit: AllAnimations.CIRCLE_COMPASS_EXPLICIT, // future
compassTransfer: AllAnimations.CIRCLE_COMPASS_TRANSFER, // I.2 (#127)
},
Polygon: {
outline: AllAnimations.POLYGON_OUTLINE,
outlineAndFill: AllAnimations.POLYGON_OUTLINE_AND_FILL,
superpose: AllAnimations.POLYGON_SUPERPOSE,
equilateralBuild: AllAnimations.POLYGON_EQUILATERAL_BUILD, // I.1 (#127)
},
Sector: {
sweep: AllAnimations.SECTOR_SWEEP,
},
Group: {
cloneAside: AllAnimations.GROUP_CLONE_ASIDE,
},
instant: AllAnimations.INSTANT,
};
// One frame-driven step of an animation. An Animation.build() returns
// an array of these; the SlateAnimator drives each step's progress
// from 0 → 1 over its durationMs, then calls finalise().
export interface IAnimationStep {
durationMs: number;
// Setup hook fires once before the first tick. Used to hide the
// target element (so visual proxies can render over its final
// position without overlap) and to register ephemeral helper
// elements via slate.addEphemeral.
setup?: () => void;
// Called each frame with progress ∈ [0, 1]. Typically sets
// target.drawProgress = progress; richer animations may also tick
// ephemeral helpers.
tick: (progress: number, dtMs: number, totalMs: number) => void;
// Called on cancel, skip, or normal completion. Animations MUST:
// 1. Remove every ephemeral helper they added.
// 2. Restore the target element to its final visible state
// (target.visible = true, drawProgress = 1).
finalise: () => void;
}
// Abstract base class for all animations. Concrete subclasses live in
// the per-element-type sibling files (PointAnimations.ts,
// LineAnimations.ts, CircleAnimations.ts, PolygonAnimations.ts).
export abstract class Animation {
// Enum value the animation registers under. Mirrors Construction's
// `constructionMethod: AllConstructions` field shape.
public abstract animationMethod: AllAnimations;
// Human-readable name in the same `Type.method` shape Construction
// uses ("Line.straightEdgeConnect", "Circle.compass", …).
public abstract name: string;
// Element class the animation can animate. animateTo validates
// the target before calling build(); a mismatch emits a
// console.warn and falls through to Instant.
public abstract elementType: Function;
// Per-animation default duration AND rate. Whichever the slate's
// animationConfig overrides wins. Null on both means build()
// computes its own duration from geometry × default rate.
public defaultDurationMs?: number;
public defaultRate?: number;
public abstract build(target: GeomElement, slate: Slate, args: any): IAnimationStep[];
// #159 — names this animation CREATES at run time, given its args.
//
// A macro animation can register new, name-addressable elements partway
// through a walk: compassTransfer's `keepCircles` is the case in point,
// and a later slide legitimately addresses those names. Deck validation
// (#154) runs when the figure is BUILT, so without this those names look
// like typos and a correct deck gets a warning badge.
//
// Declaring them here lets the validator accept a name before it exists,
// while still catching genuine typos and stale targets. Default: none,
// so an animation that creates nothing needs no change.
public declaredNames(args: any): string[] { return []; }
}
// InstantAnimation — the registry's fallback. Used explicitly via
// A.instant on a slide entry to suppress an inherited animation, or
// implicitly when an unknown name lookup fails. Just finalises the
// target to its full-rendered state.
export class InstantAnimation extends Animation {
public animationMethod = AllAnimations.INSTANT;
public name = "instant";
public elementType = GeomElement;
public build(target: GeomElement, slate: Slate, args: any): IAnimationStep[] {
return [{
durationMs: 0,
tick: () => {},
finalise: () => {
target.drawProgress = 1;
target.visible = true;
},
}];
}
}
// Module-level registry. Subclass files call registerAnimation(new X())
// at import time so the registry is populated by the time any consumer
// looks anything up.
const animationsByEnum: Map<AllAnimations, Animation> = new Map();
const animationsByName: Map<string, Animation> = new Map();
export function registerAnimation(a: Animation): void {
animationsByEnum.set(a.animationMethod, a);
animationsByName.set(a.name, a);
}
// Accepts either the enum value or the string name; returns the
// registered Animation or null when the name doesn't resolve.
//
// Pure lookup: it does NOT report an unknown name. #154 moved that to
// the caller (SlateAnimator.run), which holds the slate the diagnostic
// belongs to. That also retired a module-global "already warned" Set
// which suppressed the warning for every OTHER slate on the page once
// any one slate hit the same bad name.
export function findAnimation(ref: AllAnimations | string): Animation | null {
let a: Animation | undefined;
if (typeof ref === "string") {
a = animationsByName.get(ref);
} else {
a = animationsByEnum.get(ref);
}
return a ?? null;
}
// Register the always-present Instant. Concrete animations in
// PointAnimations.ts / LineAnimations.ts / … register themselves the
// same way when their modules are imported.
registerAnimation(new InstantAnimation());
|