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:
Ivan Fontosh
2026-08-14 08:48:41 +08:00
parent 46bec1a86a
commit 1ab6ffd593
32 changed files with 2933 additions and 28 deletions
+38
View File
@@ -43,6 +43,9 @@ import type {
SceneViewEvent,
SceneViewState,
TokenId,
TokenPathSessionEvent,
TokenPathSessionState,
TokenPathTargetKind,
VideoPlaybackEvent,
VideoPlaybackState,
} from '../types';
@@ -147,6 +150,10 @@ export const ipcChannels = {
closeNpcs: 'windows.closeNpcs',
openSceneEditor: 'windows.openSceneEditor',
closeSceneEditor: 'windows.closeSceneEditor',
openTokenPathEditor: 'windows.openTokenPathEditor',
closeTokenPathEditor: 'windows.closeTokenPathEditor',
getTokenPathEditorTarget: 'windows.getTokenPathEditorTarget',
tokenPathEditorTargetChanged: 'windows.tokenPathEditorTargetChanged',
syncChromeTitles: 'windows.syncChromeTitles',
getPresentationContentSize: 'windows.getPresentationContentSize',
presentationContentSizeChanged: 'windows.presentationContentSizeChanged',
@@ -197,6 +204,11 @@ export const ipcChannels = {
dispatch: 'sceneTokensSession.dispatch',
stateChanged: 'sceneTokensSession.stateChanged',
},
tokenPathSession: {
getState: 'tokenPathSession.getState',
dispatch: 'tokenPathSession.dispatch',
stateChanged: 'tokenPathSession.stateChanged',
},
tokenGridSnap: {
getState: 'tokenGridSnap.getState',
setEnabled: 'tokenGridSnap.setEnabled',
@@ -298,6 +310,11 @@ export type IpcEventMap = {
[ipcChannels.sceneView.stateChanged]: { state: SceneViewState };
[ipcChannels.tokens.stateChanged]: { tokens: AppToken[] };
[ipcChannels.sceneTokensSession.stateChanged]: { state: SceneTokensSessionState };
[ipcChannels.tokenPathSession.stateChanged]: { state: TokenPathSessionState };
[ipcChannels.windows.tokenPathEditorTargetChanged]: {
kind: TokenPathTargetKind;
placementId: string;
} | null;
[ipcChannels.tokenGridSnap.stateChanged]: { enabled: boolean };
[ipcChannels.players.stateChanged]: { players: AppPlayer[]; teams: AppPlayerTeam[] };
[ipcChannels.players.upsertProgress]: PlayersUpsertProgressEvent;
@@ -694,6 +711,18 @@ export type IpcInvokeMap = {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.openTokenPathEditor]: {
req: { kind: TokenPathTargetKind; placementId: string };
res: { ok: true };
};
[ipcChannels.windows.closeTokenPathEditor]: {
req: Record<string, never>;
res: { ok: true };
};
[ipcChannels.windows.getTokenPathEditorTarget]: {
req: Record<string, never>;
res: { kind: TokenPathTargetKind; placementId: string } | null;
};
[ipcChannels.windows.syncChromeTitles]: {
req: { localeTag: string };
res: { ok: true };
@@ -774,6 +803,14 @@ export type IpcInvokeMap = {
req: { event: SceneTokensSessionEvent };
res: { ok: true };
};
[ipcChannels.tokenPathSession.getState]: {
req: Record<string, never>;
res: { state: TokenPathSessionState };
};
[ipcChannels.tokenPathSession.dispatch]: {
req: { event: TokenPathSessionEvent };
res: { ok: true };
};
[ipcChannels.tokenGridSnap.getState]: {
req: Record<string, never>;
res: { enabled: boolean };
@@ -887,6 +924,7 @@ export type LegacyIpcEventMap = {
[ipcChannels.sceneView.stateChanged]: { state: SceneViewState };
[ipcChannels.tokens.stateChanged]: { tokens: AppToken[] };
[ipcChannels.sceneTokensSession.stateChanged]: { state: SceneTokensSessionState };
[ipcChannels.tokenPathSession.stateChanged]: { state: TokenPathSessionState };
[ipcChannels.tokenGridSnap.stateChanged]: { enabled: boolean };
[ipcChannels.players.stateChanged]: { players: AppPlayer[]; teams: AppPlayerTeam[] };
[ipcChannels.players.upsertProgress]: PlayersUpsertProgressEvent;