feat(tokens): animated paths for scene and NPC tokens
Add path editor window, session playback on control/presentation, and RMB controls. Fix live pose clock so motion no longer freezes after ~250ms. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,6 +37,7 @@ function NpcSprite({
|
||||
ny,
|
||||
viewport,
|
||||
editable,
|
||||
dragEnabled,
|
||||
displayScale,
|
||||
gridFit,
|
||||
disposition,
|
||||
@@ -51,6 +52,7 @@ function NpcSprite({
|
||||
ny: number;
|
||||
viewport: Viewport;
|
||||
editable: boolean;
|
||||
dragEnabled: boolean;
|
||||
displayScale: number;
|
||||
gridFit: number;
|
||||
disposition: NpcDisposition;
|
||||
@@ -74,6 +76,7 @@ function NpcSprite({
|
||||
const pos = localPos ?? { nx, ny };
|
||||
const minDim = Math.min(viewport.w, viewport.h);
|
||||
const sizePx = Math.max(16, placement.sizeN * gridFit * displayScale * minDim);
|
||||
const canDrag = editable && dragEnabled && onMove !== undefined;
|
||||
|
||||
const point = (e: React.PointerEvent) => {
|
||||
const host = e.currentTarget.parentElement;
|
||||
@@ -94,13 +97,14 @@ function NpcSprite({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[styles.token, editable ? styles.editable : ''].filter(Boolean).join(' ')}
|
||||
className={[styles.token, canDrag ? styles.editable : ''].filter(Boolean).join(' ')}
|
||||
data-testid={`session-npc-token-${placement.id}`}
|
||||
style={{
|
||||
left: viewport.x + pos.nx * viewport.w,
|
||||
top: viewport.y + pos.ny * viewport.h,
|
||||
width: sizePx,
|
||||
height: sizePx,
|
||||
pointerEvents: canDrag || onContextMenu ? 'auto' : undefined,
|
||||
}}
|
||||
onContextMenu={
|
||||
onContextMenu
|
||||
@@ -112,7 +116,7 @@ function NpcSprite({
|
||||
: undefined
|
||||
}
|
||||
onPointerDown={
|
||||
editable && onMove !== undefined
|
||||
canDrag
|
||||
? (e) => {
|
||||
if (e.button !== 0) return;
|
||||
e.preventDefault();
|
||||
@@ -132,7 +136,7 @@ function NpcSprite({
|
||||
: undefined
|
||||
}
|
||||
onPointerMove={
|
||||
editable && onMove
|
||||
canDrag
|
||||
? (e) => {
|
||||
const drag = dragRef.current;
|
||||
if (drag?.pointerId !== e.pointerId) return;
|
||||
@@ -173,6 +177,8 @@ export function SceneNpcTokensOverlay({
|
||||
onMove,
|
||||
onContextMenu,
|
||||
snapNorm,
|
||||
poseOverrides = null,
|
||||
dragEnabledById = null,
|
||||
}: {
|
||||
placements: readonly SceneNpcToken[];
|
||||
library: readonly ProjectNpc[];
|
||||
@@ -184,6 +190,8 @@ export function SceneNpcTokensOverlay({
|
||||
onMove?: (placementId: string, nx: number, ny: number) => void;
|
||||
onContextMenu?: (e: React.MouseEvent, placement: SceneNpcToken) => void;
|
||||
snapNorm?: (nx: number, ny: number) => { nx: number; ny: number };
|
||||
poseOverrides?: Record<string, { nx: number; ny: number }> | null;
|
||||
dragEnabledById?: Record<string, boolean> | null;
|
||||
}) {
|
||||
if (!viewport) return null;
|
||||
const byId = new Map(library.map((npc) => [npc.id, npc]));
|
||||
@@ -194,18 +202,22 @@ export function SceneNpcTokensOverlay({
|
||||
{placements.map((placement) => {
|
||||
const npc = byId.get(placement.npcId);
|
||||
if (!npc) return null;
|
||||
const override = session?.byPlacementId[String(placement.id)];
|
||||
const key = String(placement.id);
|
||||
const pose = poseOverrides?.[key];
|
||||
const override = session?.byPlacementId[key];
|
||||
const disposition = resolveNpcTokenDisposition(placement, npc, override?.disposition);
|
||||
const inactive = Boolean(override?.inactive);
|
||||
const dragEnabled = dragEnabledById ? Boolean(dragEnabledById[key]) : true;
|
||||
return (
|
||||
<NpcSprite
|
||||
key={placement.id}
|
||||
placement={placement}
|
||||
npc={npc}
|
||||
nx={override?.nx ?? placement.nx}
|
||||
ny={override?.ny ?? placement.ny}
|
||||
nx={pose?.nx ?? override?.nx ?? placement.nx}
|
||||
ny={pose?.ny ?? override?.ny ?? placement.ny}
|
||||
viewport={viewport}
|
||||
editable={editable}
|
||||
dragEnabled={dragEnabled}
|
||||
displayScale={displayScale}
|
||||
gridFit={gridFit}
|
||||
disposition={disposition}
|
||||
|
||||
Reference in New Issue
Block a user