import { callRef, createEffect, onCleanup, untrack, useContext } from "@solidrt/core" import type { VoidComponent } from "@solidrt/core" import { addSprite, destroySprite, setSprite, setSpriteTimeScale, setSpriteTransition } from "../layer.ts" import { setInstanceStyle } from "../records.ts" import type { Sprite as SpriteHandle, SpriteOptions, SpritePointerEvent, SpriteTapEvent, SpriteWheelEvent, TransitionEndEvent } from "../layer.ts" import type { SpriteTransition } from "../layer.ts" import { GroupContext, LayerContext } from "./context.ts" /** * Sprite pointer events, the element vocabulary one tree deeper: the * topmost hit sprite receives the event, down/move/up/wheel/tap bubble to * enclosing Groups and end at the `` (stopPropagation stops * the walk; stopping a down claims the whole press), enter/leave pair on * the sprite alone. Events flow while the element showing the layer * carries layer.handlers - the built-in `` leaf does (opt * out with events={false}); an `output` leaf spreads them itself. */ export type SpritePointerProps = BubblingSpritePointerProps & { /** Sprites only: a Group never receives enter/leave. */ onPointerEnter?: (event: SpritePointerEvent) => void onPointerLeave?: (event: SpritePointerEvent) => void } /** The pointer props a `` takes as well: the events that bubble * from the hit sprite up through the Groups above it, without the hover * pair (@solidrt/3d's BubblingNodePointerProps). */ export type BubblingSpritePointerProps = { onPointerDown?: (event: SpritePointerEvent) => void onPointerMove?: (event: SpritePointerEvent) => void onPointerUp?: (event: SpritePointerEvent) => void /** The pointer's other end: the system took it away (see core's * onPointerCancel); bubbles like up, and the press it ends never taps. */ onPointerCancel?: (event: SpritePointerEvent) => void onWheel?: (event: SpriteWheelEvent) => void /** A press released on the sprite without dragging; `tapCount` counts * repeats (2 = double tap). */ onTap?: (event: SpriteTapEvent) => void } export type SpriteProps = SpriteOptions & SpritePointerProps & { /** How pose-prop changes animate (see setSpriteTransition). The mount * pose snaps, unless a component's `from` animates it in from there. */ transition?: SpriteTransition | string | null /** A declared transition settled on one component. */ onTransitionEnd?: (event: TransitionEndEvent) => void /** The rate this sprite's native motion runs at (see * setSpriteTimeScale): 0 freezes it, 1 is app time, overriding the * layer's and the enclosing groups'. Undefined or null inherits. Node * layers only. */ timeScale?: number | null /** The sprite's style record under the layer's material (see * setInstanceStyle): one value per component of the material's first * instance buffer. Absent, the sprite keeps the material's * instanceStyle. */ style?: ArrayLike ref?: (sprite: SpriteHandle) => void } /** One sprite: a frame drawn at a position (in the enclosing ``'s * frame when there is one). */ export let Sprite: VoidComponent = props => { let layer = useContext(LayerContext) let parent = useContext(GroupContext) let sprite = untrack(() => addSprite(layer, { parent })) createEffect( () => [props.x, props.y, props.w, props.h, props.frame, props.flipX, props.flipY, props.rotation, props.tint, props.minScreenPx, props.maxScreenPx, props.renderOrder, props.visible] as const, ([x, y, w, h, frame, flipX, flipY, rotation, tint, minScreenPx, maxScreenPx, renderOrder, visible]) => setSprite(sprite, { x, y, w, h, frame, flipX, flipY, rotation, tint, minScreenPx, maxScreenPx, renderOrder, visible: visible !== false }), ) // After the pose effect, so the mount pose snaps before writes animate. createEffect( () => props.transition, transition => setSpriteTransition(sprite, transition ?? null), ) // Built only when the prop is present (the compiler emits the getter // only then): a plain sprite pays no effect, and a record layer's // sprite never meets the throw. if ("timeScale" in props) { createEffect( () => props.timeScale, scale => setSpriteTimeScale(sprite, scale ?? null), ) } if ("style" in props) { createEffect( () => props.style, style => { if (style !== undefined) setInstanceStyle(layer, sprite, style) }, ) } createEffect( () => [props.onPointerDown, props.onPointerMove, props.onPointerUp, props.onPointerCancel, props.onPointerEnter, props.onPointerLeave, props.onWheel, props.onTap, props.onTransitionEnd] as const, ([down, move, up, cancel, enter, leave, wheel, tap, end]) => { sprite.onPointerDown = down sprite.onPointerMove = move sprite.onPointerUp = up sprite.onPointerCancel = cancel sprite.onPointerEnter = enter sprite.onPointerLeave = leave sprite.onWheel = wheel sprite.onTap = tap sprite.onTransitionEnd = end }, ) callRef(() => props.ref, sprite) onCleanup(() => destroySprite(sprite)) return null }