web based infinite canvas
at main 63 lines 1.8 kB view raw
1import { BehaviorSubject, type Subscription } from "rxjs"; 2import { Vec2 } from "./math"; 3 4/** 5 * Cursor position + timing in world/screen space. 6 * 7 * CursorState is intentionally separate from EditorState so it can be updated 8 * with high frequency (e.g., on pointer move) without touching history or 9 * triggering document persistence. 10 */ 11export type CursorState = { cursorWorld: Vec2; cursorScreen?: Vec2; lastMoveAt: number }; 12 13export const CursorState = { 14 /** 15 * Create a cursor state positioned at origin with no screen point. 16 */ 17 create(world?: Vec2, screen?: Vec2, timestamp = Date.now()): CursorState { 18 return { 19 cursorWorld: Vec2.clone(world ?? { x: 0, y: 0 }), 20 cursorScreen: screen ? Vec2.clone(screen) : undefined, 21 lastMoveAt: timestamp, 22 }; 23 }, 24}; 25 26export type CursorListener = (state: CursorState) => void; 27 28/** 29 * Store that tracks cursor movement separately from the undoable editor state. 30 */ 31export class CursorStore { 32 private readonly state$: BehaviorSubject<CursorState>; 33 34 constructor(initialState?: CursorState) { 35 this.state$ = new BehaviorSubject(initialState ?? CursorState.create()); 36 } 37 38 /** 39 * Read the latest cursor snapshot. 40 */ 41 getState(): CursorState { 42 return this.state$.value; 43 } 44 45 /** 46 * Subscribe to cursor updates. 47 */ 48 subscribe(listener: CursorListener): () => void { 49 const subscription: Subscription = this.state$.subscribe(listener); 50 return () => subscription.unsubscribe(); 51 } 52 53 /** 54 * Update the cursor position without touching editor history/persistence. 55 */ 56 updateCursor(world: Vec2, screen?: Vec2, timestamp = Date.now()): void { 57 this.state$.next({ 58 cursorWorld: Vec2.clone(world), 59 cursorScreen: screen ? Vec2.clone(screen) : undefined, 60 lastMoveAt: timestamp, 61 }); 62 } 63}