Skip to content

Commit 2340872

Browse files
committed
update LuaAPI
1 parent 8282164 commit 2340872

3 files changed

Lines changed: 1669 additions & 321 deletions

File tree

app.jsx

Lines changed: 147 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,152 @@ const EASINGS = [
1717
["circIn", "circOut", "circInOut"],
1818
];
1919

20+
/* Easing math — mirrors flixel's FlxEase (Penner equations) so previews match in-game. */
21+
const EASE = (() => {
22+
const PI = Math.PI;
23+
const c1 = 1.70158, c2 = c1 * 1.525, c3 = c1 + 1, c4 = (2 * PI) / 3, c5 = (2 * PI) / 4.5;
24+
const bo = (t) => {
25+
const n = 7.5625, d = 2.75;
26+
if (t < 1 / d) return n * t * t;
27+
if (t < 2 / d) return n * (t -= 1.5 / d) * t + 0.75;
28+
if (t < 2.5 / d) return n * (t -= 2.25 / d) * t + 0.9375;
29+
return n * (t -= 2.625 / d) * t + 0.984375;
30+
};
31+
return {
32+
linear: (t) => t,
33+
sineIn: (t) => 1 - Math.cos((t * PI) / 2),
34+
sineOut: (t) => Math.sin((t * PI) / 2),
35+
sineInOut: (t) => -(Math.cos(PI * t) - 1) / 2,
36+
quadIn: (t) => t * t,
37+
quadOut: (t) => 1 - (1 - t) * (1 - t),
38+
quadInOut: (t) => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),
39+
cubeIn: (t) => t * t * t,
40+
cubeOut: (t) => 1 - Math.pow(1 - t, 3),
41+
cubeInOut: (t) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),
42+
quartIn: (t) => t * t * t * t,
43+
quartOut: (t) => 1 - Math.pow(1 - t, 4),
44+
quartInOut: (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2),
45+
quintIn: (t) => t * t * t * t * t,
46+
quintOut: (t) => 1 - Math.pow(1 - t, 5),
47+
quintInOut: (t) => (t < 0.5 ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2),
48+
expoIn: (t) => (t === 0 ? 0 : Math.pow(2, 10 * t - 10)),
49+
expoOut: (t) => (t === 1 ? 1 : 1 - Math.pow(2, -10 * t)),
50+
expoInOut: (t) => (t === 0 ? 0 : t === 1 ? 1 : t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2),
51+
backIn: (t) => c3 * t * t * t - c1 * t * t,
52+
backOut: (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2),
53+
backInOut: (t) => (t < 0.5 ? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2),
54+
elasticIn: (t) => (t === 0 ? 0 : t === 1 ? 1 : -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4)),
55+
elasticOut: (t) => (t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1),
56+
elasticInOut: (t) => (t === 0 ? 0 : t === 1 ? 1 : t < 0.5 ? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2 : (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1),
57+
bounceIn: (t) => 1 - bo(1 - t),
58+
bounceOut: bo,
59+
bounceInOut: (t) => (t < 0.5 ? (1 - bo(1 - 2 * t)) / 2 : (1 + bo(2 * t - 1)) / 2),
60+
circIn: (t) => 1 - Math.sqrt(1 - t * t),
61+
circOut: (t) => Math.sqrt(1 - (t - 1) * (t - 1)),
62+
circInOut: (t) => (t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2 : (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2),
63+
};
64+
})();
65+
66+
/* Animated curve + motion preview for a single easing. */
67+
function EasingPreview({ name, compact }) {
68+
const fn = EASE[name] || EASE.linear;
69+
const W = 280, H = 220, X0 = 36, X1 = 262, Y0 = 180, Y1 = 40; // Y0 = value 0, Y1 = value 1
70+
const tx = (t) => X0 + t * (X1 - X0);
71+
const vy = (v) => Y0 - v * (Y0 - Y1);
72+
// static curve path for the current easing
73+
let d = "";
74+
const N = 96;
75+
for (let i = 0; i <= N; i++) {
76+
const t = i / N;
77+
d += (i ? " L" : "M") + tx(t).toFixed(1) + " " + vy(fn(t)).toFixed(1);
78+
}
79+
// compress motion track to [-0.3, 1.3] so overshoot stays visible
80+
const puckPos = (v) => ((v + 0.3) / 1.6) * 100;
81+
82+
const dot = useRef(null), gx = useRef(null), gy = useRef(null), puck = useRef(null);
83+
useEffect(() => {
84+
let raf, start = null;
85+
const RUN = 1500, HOLD = 450, TOTAL = RUN + HOLD;
86+
const tick = (now) => {
87+
if (start == null) start = now;
88+
let t = ((now - start) % TOTAL) / RUN;
89+
if (t > 1) t = 1;
90+
const v = fn(t), x = tx(t), y = vy(v);
91+
if (dot.current) { dot.current.setAttribute("cx", x); dot.current.setAttribute("cy", y); }
92+
if (gx.current) { gx.current.setAttribute("x2", x); gx.current.setAttribute("y1", y); gx.current.setAttribute("y2", y); }
93+
if (gy.current) { gy.current.setAttribute("x1", x); gy.current.setAttribute("x2", x); gy.current.setAttribute("y1", y); }
94+
if (puck.current) puck.current.style.left = puckPos(v) + "%";
95+
raf = requestAnimationFrame(tick);
96+
};
97+
raf = requestAnimationFrame(tick);
98+
return () => cancelAnimationFrame(raf);
99+
}, [name]);
100+
101+
return (
102+
<React.Fragment>
103+
<svg className="ease-graph" viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${name} easing curve`}>
104+
<line className="ease-grid" x1={X0} y1={Y1} x2={X1} y2={Y1} />
105+
<line className="ease-axis" x1={X0} y1={Y1 - 18} x2={X0} y2={Y0 + 18} />
106+
<line className="ease-axis" x1={X0} y1={Y0} x2={X1} y2={Y0} />
107+
<text className="ease-tick" x={X0 - 9} y={Y0 + 4} textAnchor="end">0</text>
108+
<text className="ease-tick" x={X0 - 9} y={Y1 + 4} textAnchor="end">1</text>
109+
<text className="ease-tick" x={X0} y={Y0 + 30} textAnchor="middle">t=0</text>
110+
<text className="ease-tick" x={X1} y={Y0 + 30} textAnchor="middle">t=1</text>
111+
<line className="ease-linear" x1={X0} y1={Y0} x2={X1} y2={Y1} />
112+
<line ref={gy} className="ease-guide" x1={X0} y1={Y0} x2={X0} y2={Y0} />
113+
<line ref={gx} className="ease-guide" x1={X0} y1={Y0} x2={X0} y2={Y0} />
114+
<path className="ease-curve" d={d} />
115+
<circle ref={dot} className="ease-dot" cx={X0} cy={Y0} r="4.5" />
116+
</svg>
117+
{compact ? (
118+
<div className="ease-active">{name}</div>
119+
) : (
120+
<div className="ease-demo">
121+
<div className="ease-active">{name}</div>
122+
<div className="ease-demo-cap">value 0 → 1 over time</div>
123+
<div className="ease-track">
124+
<span className="ease-tick-mark" style={{ left: puckPos(0) + "%" }}></span>
125+
<span className="ease-tick-mark" style={{ left: puckPos(1) + "%" }}></span>
126+
<div ref={puck} className="ease-puck" style={{ left: puckPos(0) + "%" }}></div>
127+
</div>
128+
</div>
129+
)}
130+
</React.Fragment>
131+
);
132+
}
133+
20134
function EasingsSection() {
21135
const [hover, setHover] = useState(null);
136+
const [selected, setSelected] = useState("elasticOut");
137+
const [docked, setDocked] = useState(false);
138+
const cardRef = useRef(null);
139+
const active = hover || selected;
140+
141+
// When the inline preview scrolls up out of view, show a compact docked copy
142+
// so the curve stays visible while hovering rows further down the table.
143+
useEffect(() => {
144+
const el = cardRef.current;
145+
if (!el || typeof IntersectionObserver === "undefined") return;
146+
const io = new IntersectionObserver(
147+
([e]) => setDocked(!e.isIntersecting && e.boundingClientRect.top < 60),
148+
{ threshold: 0, rootMargin: "-56px 0px 0px 0px" }
149+
);
150+
io.observe(el);
151+
return () => io.disconnect();
152+
}, []);
153+
22154
return (
23155
<div>
24156
<h2 id="list">Available easings</h2>
25-
<p>Unknown names fall back to <code>linear</code>. Click an easing to preview its curve.</p>
157+
<p>Hover or click an easing to preview its curve. Unknown names fall back to <code>linear</code>.</p>
158+
<div className="ease-card" ref={cardRef}>
159+
<EasingPreview name={active} />
160+
</div>
161+
{docked && (
162+
<div className="ease-float" aria-hidden="true">
163+
<EasingPreview name={active} compact />
164+
</div>
165+
)}
26166
<table className="tbl">
27167
<thead>
28168
<tr><th>Family</th><th>In</th><th>Out</th><th>InOut</th></tr>
@@ -40,10 +180,12 @@ function EasingsSection() {
40180
<td key={idx}
41181
onMouseEnter={() => setHover(name)}
42182
onMouseLeave={() => setHover(null)}
183+
onClick={() => setSelected(name)}
43184
style={{
44-
cursor: "default",
45-
color: hover === name ? "var(--violet-strong)" : "var(--violet)",
46-
background: hover === name ? "var(--violet-soft)" : "transparent",
185+
cursor: "pointer",
186+
color: active === name ? "var(--violet-strong)" : "var(--violet)",
187+
background: active === name ? "var(--violet-soft)" : "transparent",
188+
fontWeight: selected === name ? 700 : 400,
47189
transition: "background 0.12s",
48190
}}
49191
>
@@ -59,7 +201,7 @@ function EasingsSection() {
59201
<CodeBlock lang="lua" filename="usage.lua" source={`startTween('flourish', 'logo',
60202
{ angle = 360, alpha = 0.5 },
61203
2.0,
62-
{ ease = '${hover || "elasticOut"}' })`} />
204+
{ ease = '${active}' })`} />
63205
</div>
64206
);
65207
}

0 commit comments

Comments
 (0)