UNPKG

@pixi/animate

Version:

PIXI plugin for the PixiAnimate Extension

1 lines 160 kB
{"version":3,"file":"pixi-animate.mjs","sources":["../src/animate/utils.ts","../src/animate/load.ts","../src/animate/sound.ts","../src/animate/Tween.ts","../src/animate/Timeline.ts","../src/animate/Container.ts","../src/animate/MovieClip.ts","../src/animate/Scene.ts","../src/animate/AnimatorTimeline.ts","../src/animate/Animator.ts","../src/animate/Sprite.ts","../src/animate/Graphics.ts","../src/animate/Text.ts","../src/index.ts"],"sourcesContent":["import type { DrawCommands } from './Graphics';\nimport type { TweenProps, KeyframeData, TweenData, TweenablePropNames } from './Tween';\nimport type { DisplayObject } from '@pixi/display';\nimport type { Renderer } from '@pixi/core';\nimport type { Prepare } from '@pixi/prepare';\nimport type { MovieClip } from './MovieClip';\n\n// If the movieclip plugin is installed\nlet _prepare: Prepare = null;\n\n/* eslint-disable @typescript-eslint/no-namespace, no-inner-declarations */\n// awkwardly named instead of the final export of 'utils' to avoid problems in .d.ts build tools.\nexport namespace utils\n{\n\n /**\n * Convert the Hexidecimal string (e.g., \"#fff\") to uint\n */\n export function hexToUint(hex: string): number\n {\n // Remove the hash\n hex = hex.substr(1);\n\n // Convert shortcolors fc9 to ffcc99\n if (hex.length === 3)\n {\n hex = hex.replace(/([a-f0-9])/g, '$1$1');\n }\n\n return parseInt(hex, 16);\n }\n\n /**\n * Fill frames with booleans of true (showing) and false (hidden).\n * @param timeline -\n * @param startFrame - The start frame when the timeline shows up\n * @param duration - The length of showing\n */\n export function fillFrames(timeline: boolean[], startFrame: number, duration: number): void\n {\n // ensure that the timeline is long enough\n const oldLength = timeline.length;\n\n if (oldLength < startFrame + duration)\n {\n timeline.length = startFrame + duration;\n // fill any gaps with false to denote that the child should be removed for a bit\n if (oldLength < startFrame)\n {\n // if the browser has implemented the ES6 fill() function, use that\n if (timeline.fill)\n {\n timeline.fill(false, oldLength, startFrame);\n }\n else\n {\n // if we can't use fill, then do a for loop to fill it\n for (let i = oldLength; i < startFrame; ++i)\n {\n timeline[i] = false;\n }\n }\n }\n }\n // if the browser has implemented the ES6 fill() function, use that\n if (timeline.fill)\n {\n timeline.fill(true, startFrame, startFrame + duration);\n }\n else\n {\n const length = timeline.length;\n // if we can't use fill, then do a for loop to fill it\n\n for (let i = startFrame; i < length; ++i)\n {\n timeline[i] = true;\n }\n }\n }\n\n const keysMap: {[s: string]: keyof TweenProps} = {\n X: 'x', // x position\n Y: 'y', // y position\n A: 'sx', // scale x\n B: 'sy', // scale y\n C: 'kx', // skew x\n D: 'ky', // skew y\n R: 'r', // rotation\n L: 'a', // alpha\n T: 't', // tint\n F: 'c', // colorTransform\n V: 'v', // visibility\n };\n\n /**\n * Parse the value of the compressed keyframe.\n * @param prop - The property key\n * @param buffer - The contents\n * @return The parsed value\n */\n function parseValue(prop: string, buffer: string): string | number | boolean | (string | number)[]\n {\n switch (prop)\n {\n // Color transforms are parsed as an array\n case 'c':\n {\n const buff: (string | number)[] = buffer.split(',');\n\n buff.forEach((val, i, buffer) =>\n {\n buffer[i] = parseFloat(val as string);\n });\n\n return buff;\n }\n // Tint value should not be converted\n // can be color uint or string\n case 't':\n {\n return buffer;\n }\n // The visiblity parse as boolean\n case 'v':\n {\n return !!parseInt(buffer, 10);\n }\n // Everything else parse a floats\n default:\n {\n return parseFloat(buffer);\n }\n }\n }\n\n const tweenKeysMap: { [s: string]: keyof TweenData } = {\n D: 'd', // duration\n // E: 'e', // easing - disabled for manual handling\n P: 'p', // props\n };\n\n /**\n * Regex to test for a basic ease desccriptor\n */\n const basicEase = /(\\-?\\d*\\.?\\d*)([a-zA-Z]+)/;\n\n /**\n * Convert serialized tween from a serialized keyframe into TweenData\n * `\"D20E25EaseIn;PX3Y5A1.2\"` to: `{ d: 20, e: { s: 25, n: \"EaseIn\" }, p: { x:3, y: 5, sx: 1.2 } }`\n * @param tweenBuffer -\n * @return Resulting TweenData\n */\n function parseTween(tweenBuffer: string): TweenData\n {\n const result: TweenData = { d: 0, p: {} };\n\n let i = 0;\n let buffer = '';\n let handlingProps = false;\n let prop: keyof TweenProps | keyof TweenData;\n\n // tween format:\n // D20E25EaseIn;PX3Y5A1.2\n\n while (i <= tweenBuffer.length)\n {\n const c = tweenBuffer[i];\n\n if (!handlingProps && (tweenKeysMap[prop] || tweenKeysMap[c]))\n {\n // handle potential active duration property, which is the only normal one\n if (prop === 'd')\n {\n (result.d as any) = parseValue(prop, buffer);\n prop = null;\n }\n\n // seeing the p property kicks us immediately into props mode\n if (c === 'P')\n {\n handlingProps = true;\n ++i;\n }\n else\n {\n // only handles D, really\n prop = tweenKeysMap[c];\n ++i;\n }\n buffer = '';\n }\n // seeing easing means we need to read ahead to the end of the easing section\n else if (c === 'E')\n {\n // search for the next space or end of the string to see where the tween ends\n let index = tweenBuffer.indexOf(';', i);\n\n // should never end early, but just in case we are somehow tweening 0 properties\n if (index < 0)\n {\n index = tweenBuffer.length;\n }\n const easeBuffer = tweenBuffer.substring(i + 1, index);\n\n if (basicEase.test(easeBuffer))\n {\n const [, strength, name] = basicEase.exec(easeBuffer);\n\n // if not yet handling props, apply ease to whole tween\n if (!handlingProps)\n {\n result.e = {\n s: parseFloat(strength),\n n: name,\n };\n }\n // apply ease to last property read\n else if (prop)\n {\n (result.p[prop as TweenablePropNames] as any) = parseValue(prop, buffer);\n if (!result.p.e)\n {\n result.p.e = {};\n }\n result.p.e[prop as TweenablePropNames] = {\n s: parseFloat(strength),\n n: name,\n };\n prop = null;\n buffer = '';\n }\n }\n else\n {\n // TODO: encode some sort of function for a custom ease\n }\n\n i = index + 1;\n }\n // normal prop/buffer handling, like in the main deserializeKeyframes function\n else if (keysMap[c])\n {\n if (prop)\n {\n (result.p[prop as keyof TweenProps] as any) = parseValue(prop, buffer);\n }\n prop = keysMap[c];\n buffer = '';\n i++;\n }\n else if (!c)\n {\n if (prop)\n {\n (result.p[prop as keyof TweenProps] as any) = parseValue(prop, buffer);\n }\n buffer = '';\n prop = null;\n i++;\n }\n else\n {\n buffer += c;\n i++;\n }\n }\n\n return result;\n }\n\n /**\n * Convert serialized array into keyframes\n * `\"0x100y100 1x150\"` to: `{ \"0\": {\"x\":100, \"y\": 100}, \"1\": {\"x\": 150} }`\n * @param keyframes -\n * @return Resulting keyframes\n */\n export function deserializeKeyframes(keyframes: string): {[s: number]: KeyframeData}\n {\n const result: {[s: number]: KeyframeData} = {};\n let i = 0;\n\n let buffer = '';\n let isFrameStarted = false;\n let prop: keyof TweenProps;\n let frame: KeyframeData = {};\n\n while (i <= keyframes.length)\n {\n const c = keyframes[i];\n\n // if we found the name of a property\n if (keysMap[c])\n {\n // start a new frame if we need to\n if (!isFrameStarted)\n {\n isFrameStarted = true;\n result[buffer as any] = frame;\n }\n // finish a previous prop if one is running\n if (prop)\n {\n (frame[prop] as any) = parseValue(prop, buffer);\n }\n // save the new prop that we are now handling\n prop = keysMap[c];\n // reset buffer (because we did the previous prop if we had to)\n buffer = '';\n i++;\n }\n // contains a tween\n else if (c === 'W')\n {\n // start a new frame if we need to\n if (!isFrameStarted)\n {\n isFrameStarted = true;\n result[buffer as any] = frame;\n }\n // finish previous prop\n if (prop)\n {\n (frame[prop] as any) = parseValue(prop, buffer);\n buffer = '';\n prop = null;\n }\n // search for the next space or end of the string to see where the tween ends\n let index = keyframes.indexOf(' ', i);\n\n if (index < 0)\n {\n index = keyframes.length;\n }\n // parse the tween section\n frame.tw = parseTween(keyframes.substring(i + 1, index));\n // skip past the tween section\n i = index;\n }\n // finish existing prop & frame on end of string or space\n else if (!c || c === ' ')\n {\n i++;\n if (prop)\n {\n (frame[prop] as any) = parseValue(prop, buffer);\n }\n buffer = '';\n prop = null;\n frame = {};\n isFrameStarted = false;\n }\n // add to the buffer for the next parse\n else\n {\n buffer += c;\n i++;\n }\n }\n\n return result;\n }\n\n /**\n * Convert serialized shapes into draw commands for PIXI.Graphics.\n * @param str -\n */\n export function deserializeShapes(str: string): DrawCommands[]\n {\n const result = [];\n // each shape is a new line\n const shapes = str.split('\\n');\n const isCommand = /^[a-z]{1,2}$/;\n\n for (let i = 0; i < shapes.length; i++)\n {\n const shape: DrawCommands = shapes[i].split(' '); // arguments are space separated\n\n for (let j = 0; j < shape.length; j++)\n {\n // Convert all numbers to floats, ignore colors\n const arg = shape[j] as string;\n\n if (arg[0] !== '#' && !isCommand.test(arg))\n {\n shape[j] = parseFloat(arg);\n }\n }\n result.push(shape);\n }\n\n return result;\n }\n\n /**\n * Add movie clips to the upload prepare.\n * @param item - item To add to the queue\n */\n export function addMovieClips(item: any): boolean\n {\n if (item.isMovieClip)\n {\n const mc = item as MovieClip;\n\n mc._timedChildTimelines.forEach((timeline) =>\n {\n const index = mc.children.indexOf(timeline.target);\n\n if (index === -1)\n {\n // eslint-disable-next-line no-unused-expressions\n _prepare?.add(timeline.target);\n }\n });\n\n return true;\n }\n\n return false;\n }\n\n /**\n * Upload all the textures and graphics to the GPU.\n * @param renderer - Render to upload to\n * @param clip - MovieClip to upload\n * @param done - When complete\n */\n export function upload(renderer: Renderer, displayObject: DisplayObject, done: () => void): void\n {\n if (!_prepare)\n {\n _prepare = renderer.plugins.prepare;\n _prepare.registerFindHook(addMovieClips);\n }\n // eslint-disable-next-line no-unused-expressions\n _prepare?.upload(displayObject).then(done);\n }\n}\n","import type { Container } from '@pixi/display';\nimport type { AnimateAsset } from '../AnimateAsset';\nimport type { MovieClip } from './MovieClip';\nimport type { DrawCommands } from './Graphics';\nimport { utils } from './utils';\nimport { Assets } from '@pixi/assets';\nimport { Texture } from '@pixi/core';\nimport { Spritesheet } from '@pixi/spritesheet';\n\ntype Complete = (instance: MovieClip | null) => void;\ntype Progress = (value: number) => void;\nexport interface LoadOptions\n{\n /**\n * The Container to auto-add the stage to, if createInstance is true.\n */\n parent?: Container;\n /**\n * Callback for load completion.\n */\n complete?: Complete;\n /**\n * Callback for load progress.\n */\n progress?: Progress;\n /**\n * Base root directory\n */\n basePath?: string;\n /**\n * Enable or disable automatic instantiation of stage - defaults to false.\n */\n createInstance?: boolean;\n /**\n * Metadata to be handed off to the loader for assets that are loaded.\n */\n metadata?: any;\n}\n\nconst EXPECTED_ASSET_VERSION = 2;\n\n/**\n * Load the stage class and preload any assets\n * ```\n * import MyAsset from './myAsset.js';\n * let renderer = new PIXI.autoDetectRenderer(1280, 720);\n * let stage = new PIXI.Container();\n * PIXI.animate.load(MyAsset, function(asset){\n * stage.addChild(new asset.stage());\n * });\n * function update() {\n * renderer.render(stage);\n * update();\n * }\n * update();\n * ```\n * @param scene - Reference to the scene data.\n * @param complete - The callback function when complete.\n * @return instance of PIXI resource loader\n */\nexport function load(scene: AnimateAsset, complete?: Complete): void;\n/**\n * Load the stage class and preload any assets\n * ```\n * import MyAsset from './myAsset.js';\n * let basePath = 'file:/path/to/assets';\n * let renderer = new PIXI.Renderer(1280, 720);\n *\n * let extensions = PIXI.compressedTextures.detectExtensions(renderer);\n * let loader = new PIXI.Loader();\n * // this is an example of setting up a pre loader plugin to handle compressed textures in this case\n * loader.pre(PIXI.compressedTextures.extensionChooser(extensions));\n *\n * // specify metadata this way if you want to provide a default loading strategy for all assets listed in the PIXI animation\n * let metadata = { default: { metadata: { imageMetadata: { choice: ['.crn'] } } } };\n * // specify metadata this way if you want to provide a specific loading strategy for a\n * // certain asset listed inside the PIXI animation library\n * let metadata = { MyStage_atlas_1: { metadata: { imageMetadata: { choice: ['.crn'] } } } };\n *\n * let stage = new PIXI.Container();\n * PIXI.animate.load(MyAsset, {\n * parent: stage,\n * complete: ()=>{},\n * basePath: basePath,\n * loader: loader,\n * metadata: metadata\n * });\n * function update() {\n * renderer.render(stage);\n * update();\n * }\n * update();\n * ```\n * @param scene - Reference to the scene data.\n * @param options - Options for loading.\n * @return instance of PIXI resource loader\n */\nexport function load(scene: AnimateAsset, options: LoadOptions): void;\nexport function load(scene: AnimateAsset, optionsOrComplete?: Complete | LoadOptions): void\n{\n const complete: Complete = typeof optionsOrComplete === 'function' ? optionsOrComplete : optionsOrComplete?.complete;\n const progress: Progress | undefined = typeof optionsOrComplete === 'function' ? undefined : optionsOrComplete?.progress;\n\n let basePath = '';\n let parent: Container = null;\n let metadata: any;\n let createInstance = false;\n\n // check scene and warn about it\n const { version } = scene;\n\n if (typeof version === 'number')\n {\n /* eslint-disable max-len */\n if (Math.floor(version) !== Math.floor(EXPECTED_ASSET_VERSION))\n {\n console.warn(`Asset version is not the major version expected of ${Math.floor(EXPECTED_ASSET_VERSION)} - it may not load properly`, scene);\n }\n else if (version > EXPECTED_ASSET_VERSION)\n {\n console.warn('Asset has been published with a newer version than PixiAnimate expects. It may not load properly.', scene);\n }\n /* eslint-enable max-len */\n }\n\n if (optionsOrComplete && typeof optionsOrComplete !== 'function')\n {\n basePath = optionsOrComplete.basePath || '';\n parent = optionsOrComplete.parent;\n metadata = optionsOrComplete.metadata;\n createInstance = !!optionsOrComplete.createInstance;\n }\n\n function done(): void\n {\n const instance = (createInstance && typeof scene.stage === 'function') ? new scene.stage() : null;\n\n if (parent && instance)\n {\n parent.addChild(instance);\n }\n if (complete)\n {\n complete(instance);\n }\n }\n\n // Check for assets to preload\n const assets = scene.assets || {};\n\n if (assets && Object.keys(assets).length)\n {\n let totalAssets = 0;\n let loadedAssets = 0;\n\n const promises: Promise<any>[] = [];\n // assetBaseDir can accept either with trailing slash or not\n\n if (basePath)\n {\n basePath += '/';\n }\n for (const id in assets)\n {\n if (progress) totalAssets++;\n\n let data = null;\n\n if (metadata)\n {\n // if the metadata was supplied for this particular asset, use these options\n if (metadata[id])\n {\n data = metadata[id];\n }\n // if the metadata supplied a default option\n else if (metadata.default)\n {\n data = metadata.default;\n }\n }\n promises.push(Assets.load({ alias: [id], src: basePath + assets[id], data }).then((loadedAsset) =>\n {\n if (progress)\n {\n loadedAssets++;\n\n progress(loadedAssets / totalAssets);\n }\n\n if (!loadedAsset)\n {\n return; // not sure if this can ever happen\n }\n if (loadedAsset instanceof Spritesheet)\n {\n scene.spritesheets.push(loadedAsset);\n }\n else if (loadedAsset instanceof Texture)\n {\n scene.textures[id] = loadedAsset;\n }\n else if (Array.isArray(loadedAsset) || typeof loadedAsset === 'string')\n {\n // save shape data\n let items: string | DrawCommands[] = loadedAsset;\n\n // Decode string to map of files\n if (typeof items === 'string')\n {\n items = utils.deserializeShapes(items);\n }\n\n // Convert all hex string colors (animate) to int (pixi.js)\n for (let i = 0; i < items.length; i++)\n {\n const item = items[i];\n\n for (let j = 0; j < item.length; j++)\n {\n const arg = item[j];\n\n if (typeof arg === 'string' && arg[0] === '#')\n {\n item[j] = utils.hexToUint(arg);\n }\n }\n }\n scene.shapes[id] = items;\n }\n }));\n }\n Promise.all(promises).then(done);\n }\n else\n {\n // tiny case where there's only text and no shapes/animations\n done();\n }\n}\n","import { EventEmitter } from '@pixi/utils';\n/**\n * @description Event emitter for all sound events. This emits a single\n * `play` event which contains the alias, loop and MovieClip which is playing\n * the sound.\n * @example\n *\n * PIXI.animate.sound.on('play', (alias, loop, context) => {\n * // custom handle sounds being played\n * // where 'alias' is the ID in stage assets\n * });\n */\nexport const sound = new EventEmitter();\n","import type { AnimateDisplayObject } from './DisplayObject';\nimport type { Graphics } from '@pixi/graphics';\nimport type { Sprite } from '@pixi/sprite';\n\nexport type EaseMethod = (input: number) => number;\n\n// NOTE ABOUT KEYS OF TweenProps: Use \"(myProps[key] as any) = myVal;\"\n// Typescript is unhelpful in this case: https://github.com/microsoft/TypeScript/issues/31663\nexport interface TweenProps\n{\n x?: number;\n y?: number;\n sx?: number;\n sy?: number;\n kx?: number;\n ky?: number;\n r?: number;\n a?: number;\n t?: number;\n v?: boolean;\n c?: number[];\n m?: Graphics | Sprite;\n g?: any;\n /** Eases for any of the tweenable properties, if published as a per-property ease */\n e?: {[P in TweenablePropNames]?: EaseMethod | {n: string; s: number}};\n}\n\nexport type TweenablePropNames = keyof Omit<TweenProps, 'm' | 'g' | 'e' | 'v'>;\n\nexport interface TweenData\n{\n d: number;\n p: TweenProps;\n e?: EaseMethod | {n: string; s: number};\n}\n\nexport interface KeyframeData extends TweenProps\n{\n /** Not tweenable, but information about a tween that starts on this frame */\n tw?: TweenData;\n}\n\n// standard tweening\nfunction lerpValue(start: number, end: number, t: number): number\n{\n return start + ((end - start) * t);\n}\n\nconst PI = Math.PI;\nconst TWO_PI = PI * 2;\n\n// handle 355 -> 5 degrees only going through a 10 degree change instead of\n// the long way around\n// Math from http://stackoverflow.com/a/2708740\nfunction lerpRotation(start: number, end: number, t: number): number\n{\n const difference = Math.abs(end - start);\n\n if (difference > PI)\n {\n // We need to add on to one of the values.\n if (end > start)\n {\n // We'll add it on to start...\n start += TWO_PI;\n }\n else\n {\n // Add it on to end.\n end += TWO_PI;\n }\n }\n\n // Interpolate it.\n const value = (start + ((end - start) * t));\n\n // wrap to 0-2PI\n /* if (value >= 0 && value <= TWO_PI)\n return value;\n return value % TWO_PI;*/\n\n // just return, as it's faster\n return value;\n}\n\n// handle 180 -> -170 degrees only going through a 10 degree change instead of\n// the long way around\n// Math from http://stackoverflow.com/a/2708740\n// We're assuming Skew values are always in the range -PI to PI\nfunction lerpSkew(start: number, end: number, t: number): number\n{\n const difference = Math.abs(end - start);\n\n if (difference > PI)\n {\n // We need to add on to one of the values.\n if (end > start)\n {\n // We'll add it on to start...\n start += TWO_PI;\n }\n else\n {\n // Add it on to end.\n end += TWO_PI;\n }\n }\n\n // Interpolate it.\n const value = (start + ((end - start) * t));\n\n // wrap to -PI to PI\n if (value > PI) return value - TWO_PI;\n if (value < -PI) return value + TWO_PI;\n\n return value;\n}\n\n// split r, g, b into separate values for tweening\nfunction lerpTint(start: number, end: number, t: number): number\n{\n // split start color into components\n const sR = (start >> 16) & 0xFF;\n const sG = (start >> 8) & 0xFF;\n const sB = start & 0xFF;\n // split end color into components\n const eR = (end >> 16) & 0xFF;\n const eG = (end >> 8) & 0xFF;\n const eB = end & 0xFF;\n // lerp red\n let r = sR + ((eR - sR) * t);\n\n // clamp red to valid values\n if (r < 0) r = 0;\n else if (r > 255) r = 255;\n // lerp green\n let g = sG + ((eG - sG) * t);\n\n // clamp green to valid values\n if (g < 0) g = 0;\n else if (g > 255) g = 255;\n // lerp blue\n let b = sB + ((eB - sB) * t);\n\n // clamp blue to valid values\n if (b < 0) b = 0;\n else if (b > 255) b = 255;\n\n const combined = (r << 16) | (g << 8) | b;\n\n return combined;\n}\n\nconst COLOR_HELPER: number[] = [];\n\nfunction lerpColor(start: number[], end: number[], t: number): number[]\n{\n COLOR_HELPER[0] = start[0] + ((end[0] - start[0]) * t);\n COLOR_HELPER[1] = start[1] + ((end[1] - start[1]) * t);\n COLOR_HELPER[2] = start[2] + ((end[2] - start[2]) * t);\n COLOR_HELPER[3] = start[3] + ((end[3] - start[3]) * t);\n COLOR_HELPER[4] = start[4] + ((end[4] - start[4]) * t);\n COLOR_HELPER[5] = start[5] + ((end[5] - start[5]) * t);\n\n return COLOR_HELPER;\n}\n\nconst PROP_LERPS: {[P in keyof TweenProps]: (start: number, end: number, t: number) => number} = {\n // position\n x: lerpValue,\n y: lerpValue,\n // scale\n sx: lerpValue,\n sy: lerpValue,\n // skew\n kx: lerpSkew,\n ky: lerpSkew,\n // rotation\n r: lerpRotation,\n // alpha\n a: lerpValue,\n // tinting\n t: lerpTint,\n // values to be set\n v: null, // visible\n c: lerpColor as any, // colorTransform\n m: null, // mask\n g: null, // not sure if we'll actually handle graphics this way?\n};\n\nfunction setPropFromShorthand(target: AnimateDisplayObject, prop: keyof TweenProps, value: any): void\n{\n switch (prop)\n {\n case 'x':\n target.transform.position.x = value;\n break;\n case 'y':\n target.transform.position.y = value;\n break;\n case 'sx':\n target.transform.scale.x = value;\n break;\n case 'sy':\n target.transform.scale.y = value;\n break;\n case 'kx':\n target.transform.skew.x = value;\n break;\n case 'ky':\n target.transform.skew.y = value;\n break;\n case 'r':\n target.transform.rotation = value;\n break;\n case 'a':\n target.alpha = value;\n break;\n case 't':\n target.i(value); // i = setTint\n break;\n case 'c':\n target.setColorTransform(...value as [number, number, number, number, number, number]); // c = setColorTransform\n break;\n case 'v':\n target.visible = value;\n break;\n case 'm':\n target.ma(value); // ma = setMask\n break;\n }\n}\n\n// builds an ease in function for a specific exponential power, i.e. quadratic easing is power 2 and cubic is 3\nfunction buildPowIn(power: number): EaseMethod\n{\n return (t): number => Math.pow(t, power);\n}\n\n// builds an ease out function for a specific exponential power, i.e. quadratic easing is power 2 and cubic is 3\nfunction buildPowOut(power: number): EaseMethod\n{\n return (t): number => 1 - Math.pow(1 - t, power);\n}\n\n// builds an ease in & out function for a specific exponential power, i.e. quadratic easing is power 2 and cubic is 3\nfunction buildPowInOut(power: number): EaseMethod\n{\n return (t): number =>\n {\n if ((t *= 2) < 1) return 0.5 * Math.pow(t, power);\n\n return 1 - (0.5 * Math.abs(Math.pow(2 - t, power)));\n };\n}\nconst ELASTIC_AMPLITUDE = 1;\nconst ELASTIC_PERIOD = 0.3;\nconst ELASTIC_INOUT_PERIOD = 0.3 * 1.5;\n\nconst EASE_DICT: { [name: string]: EaseMethod } = {\n quadIn: buildPowIn(2),\n quadOut: buildPowOut(2),\n quadInOut: buildPowInOut(2),\n cubicIn: buildPowIn(3),\n cubicOut: buildPowOut(3),\n cubicInOut: buildPowInOut(3),\n quartIn: buildPowIn(4),\n quartOut: buildPowOut(4),\n quartInOut: buildPowInOut(4),\n quintIn: buildPowIn(5),\n quintOut: buildPowOut(5),\n quintInOut: buildPowInOut(5),\n sineIn: (t) => 1 - Math.cos(t * PI / 2),\n sineOut: (t) => Math.sin(t * PI / 2),\n sineInOut: (t) => -0.5 * (Math.cos(PI * t) - 1),\n backIn: (t) => t * t * (((1.7 + 1) * t) - 1.7),\n backOut: (t) => (--t * t * (((1.7 + 1) * t) + 1.7)) + 1,\n backInOut: (t) =>\n {\n const constVal = 1.7 * 1.525;\n\n if ((t *= 2) < 1) return 0.5 * (t * t * (((constVal + 1) * t) - constVal));\n\n return 0.5 * (((t -= 2) * t * (((constVal + 1) * t) + constVal)) + 2);\n },\n circIn: (t) => -(Math.sqrt(1 - (t * t)) - 1),\n circOut: (t) => Math.sqrt(1 - ((--t) * t)),\n circInOut: (t) =>\n {\n if ((t *= 2) < 1) return -0.5 * (Math.sqrt(1 - (t * t)) - 1);\n\n return 0.5 * (Math.sqrt(1 - ((t -= 2) * t)) + 1);\n },\n bounceIn: (t) => 1 - EASE_DICT.bounceOut(1 - t),\n bounceOut: (t) =>\n {\n if (t < 1 / 2.75)\n {\n return 7.5625 * t * t;\n }\n else if (t < 2 / 2.75)\n {\n return (7.5625 * (t -= 1.5 / 2.75) * t) + 0.75;\n }\n else if (t < 2.5 / 2.75)\n {\n return (7.5625 * (t -= 2.25 / 2.75) * t) + 0.9375;\n }\n\n return (7.5625 * (t -= 2.625 / 2.75) * t) + 0.984375;\n },\n // eslint-disable-next-line no-confusing-arrow\n bounceInOut: (t) => t < 0.5 ? EASE_DICT.bounceIn(t * 2) * 0.5 : (EASE_DICT.bounceOut((t * 2) - 1) * 0.5) + 0.5,\n elasticIn: (t) =>\n {\n if (t === 0 || t === 1) return t;\n const s = ELASTIC_PERIOD / TWO_PI * Math.asin(1 / ELASTIC_AMPLITUDE);\n\n return -(ELASTIC_AMPLITUDE * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TWO_PI / ELASTIC_PERIOD));\n },\n elasticOut: (t) =>\n {\n if (t === 0 || t === 1) return t;\n const s = ELASTIC_PERIOD / TWO_PI * Math.asin(1 / ELASTIC_AMPLITUDE);\n\n return (ELASTIC_AMPLITUDE * Math.pow(2, -10 * t) * Math.sin((t - s) * TWO_PI / ELASTIC_PERIOD)) + 1;\n },\n elasticInOut: (t) =>\n {\n const s = ELASTIC_INOUT_PERIOD / TWO_PI * Math.asin(1 / ELASTIC_AMPLITUDE);\n\n if ((t *= 2) < 1)\n {\n return -0.5 * (ELASTIC_AMPLITUDE * Math.pow(2, 10 * (t -= 1))\n * Math.sin((t - s) * TWO_PI / ELASTIC_INOUT_PERIOD));\n }\n\n return (ELASTIC_AMPLITUDE * Math.pow(2, -10 * (t -= 1))\n * Math.sin((t - s) * TWO_PI / ELASTIC_INOUT_PERIOD) * 0.5) + 1;\n },\n};\n\nexport function getEaseFromConfig(config: EaseMethod | { n: string; s: number }): EaseMethod | null\n{\n if (!config) return null;\n if (typeof config === 'function') return config;\n // TODO: use config (name, strength) to determine an ease method\n // In order to figure that out, we need to test out Animate's actual output values so we know what to use.\n\n if (config.n === 'classic')\n {\n const s = config.s / 100;\n\n // (s + 1)t + (-s)(t^2)\n return (t: number): number => ((s + 1) * t) + ((-s) * t * t);\n }\n\n return EASE_DICT[config.n];\n}\n\n/**\n * Provides timeline playback of movieclip\n */\nexport class Tween\n{\n /**\n * Target display object.\n */\n public target: AnimateDisplayObject;\n /**\n * Properties at the start of the tween\n */\n public startProps: TweenProps;\n /**\n * Properties at the end of the tween, as well as any properties that are set\n * instead of tweened\n */\n public endProps: TweenProps;\n /**\n * duration of tween in frames. A single-frame keyframe has a duration of 0.\n */\n public duration: number;\n /**\n * The frame that the tween starts on\n */\n public startFrame: number;\n /**\n * the frame that the tween ends on\n */\n public endFrame: number;\n /**\n * easing function to use, if any\n */\n public ease: {[P in TweenablePropNames]?: EaseMethod};\n /**\n * If we don't tween.\n */\n public isTweenlessFrame: boolean;\n\n /**\n * @param target - The target to play\n * @param startProps - The starting properties\n * @param endProps - The ending properties\n * @param startFrame - frame number on which to begin tweening\n * @param duration - Number of frames to tween\n * @param ease - Ease function to use\n */\n constructor(target: AnimateDisplayObject,\n startProps: TweenProps,\n endProps: TweenProps | null,\n startFrame: number,\n duration: number,\n ease?: EaseMethod)\n {\n this.target = target;\n this.startProps = startProps;\n this.endProps = {};\n this.duration = duration;\n this.startFrame = startFrame;\n this.endFrame = startFrame + duration;\n this.ease = {};\n this.isTweenlessFrame = !endProps;\n\n if (endProps)\n {\n // make a copy to safely include any unchanged values from the start of the tween\n for (const prop in endProps)\n {\n if (prop === 'e') continue;\n // read the end value\n (this.endProps[prop as TweenablePropNames] as any) = endProps[prop as TweenablePropNames];\n // if there is an ease for that property, use that\n if (endProps.e?.[prop as TweenablePropNames])\n {\n this.ease[prop as TweenablePropNames] = getEaseFromConfig(endProps.e[prop as TweenablePropNames]);\n }\n // otherwise use the global ease for this tween (if any)\n else\n {\n this.ease[prop as TweenablePropNames] = ease;\n }\n }\n }\n\n // copy in any starting properties don't change\n for (const prop in startProps)\n {\n // eslint-disable-next-line no-prototype-builtins\n if (!this.endProps.hasOwnProperty(prop))\n {\n (this.endProps[prop as keyof TweenProps] as any) = startProps[prop as keyof TweenProps];\n }\n }\n }\n\n /**\n * Set the current frame.\n */\n public setPosition(currentFrame: number): void\n {\n // if this is a single frame with no tweening, or at the end of the tween, then\n // just speed up the process by setting values\n if (currentFrame >= this.endFrame)\n {\n this.setToEnd();\n\n return;\n }\n\n if (this.isTweenlessFrame)\n {\n this.setToEnd();\n\n return;\n }\n\n const time = (currentFrame - this.startFrame) / this.duration;\n\n const target = this.target;\n const startProps = this.startProps;\n const endProps = this.endProps;\n\n for (const prop in endProps)\n {\n const p = prop as keyof TweenProps;\n const lerp = PROP_LERPS[p];\n let lerpedTime = time;\n\n if (this.ease[prop as TweenablePropNames])\n {\n lerpedTime = this.ease[prop as TweenablePropNames](time);\n }\n\n if (lerp)\n {\n setPropFromShorthand(target, p, lerp(startProps[p], endProps[p], lerpedTime));\n }\n else\n {\n setPropFromShorthand(target, p, startProps[p]);\n }\n }\n }\n\n /**\n * Set to the end position\n */\n setToEnd(): void\n {\n const endProps = this.endProps;\n const target = this.target;\n\n for (const prop in endProps)\n {\n setPropFromShorthand(target, prop as keyof TweenProps, endProps[prop as keyof TweenProps]);\n }\n }\n}\n","import { Tween, TweenProps, EaseMethod } from './Tween';\nimport type { AnimateDisplayObject } from './DisplayObject';\n\n/**\n * The Timeline class represents a series of tweens, tied to keyframes.\n */\nexport class Timeline extends Array<Tween>\n{\n /**\n * The target DisplayObject.\n */\n public target: AnimateDisplayObject;\n /**\n * Current properties in the tween, to make building the timeline more\n * efficient.\n */\n private _currentProps: TweenProps;\n\n /**\n * Creates a new Timeline. Must be used instead of a constructor because extending the Array\n * class is a pain: https://blog.simontest.net/extend-array-with-typescript-965cc1134b3\n * @param target - The target for this string of tweens.\n * @returns A new Timeline instance.\n */\n public static create(target: AnimateDisplayObject): Timeline\n {\n const out = Object.create(Timeline.prototype) as Timeline;\n\n out.target = target;\n out._currentProps = {};\n\n return out;\n }\n\n // exists to be private to prevent usage\n private constructor()\n {\n super();\n }\n\n /**\n * Adds one or more tweens (or timelines) to this timeline. The tweens will be paused (to\n * remove them from the normal ticking system and managed by this timeline. Adding a tween to\n * multiple timelines will result in unexpected behaviour.\n * @param tween - The tween(s) to add. Accepts multiple arguments.\n * @return Tween The first tween that was passed in.\n */\n public addTween(properties: TweenProps, startFrame: number, duration: number, ease?: EaseMethod): void\n {\n this.extendLastFrame(startFrame - 1);\n // figure out what the starting values for this tween should be\n // ownership of startProps is passed to the new Tween - this object should not be reused\n const startProps: TweenProps = Object.assign({}, this._currentProps);\n\n for (const prop in properties)\n {\n const p = prop as keyof TweenProps;\n\n // if we have not already set that property in an earlier tween, handle that property\n if (!Object.hasOwnProperty.call(this._currentProps, prop))\n {\n const startValue = (startProps[p] as any) = this.getPropFromShorthand(p);\n\n // go through previous tweens to set the value so that when the timeline loops\n // around, the values are set properly - having each tween know what came before\n // allows us to set to a specific frame without running through the entire timeline\n for (let i = this.length - 1; i >= 0; --i)\n {\n (this[i].startProps[p] as any) = startValue;\n (this[i].endProps[p] as any) = startValue;\n }\n }\n }\n // create the new Tween and add it to the list\n const tween = new Tween(this.target, startProps, properties, startFrame, duration, ease);\n\n // if we have this frame already, replace it\n if (startFrame === this[this.length - 1].startFrame)\n {\n this[this.length - 1] = tween;\n }\n // otherwise add it to the list\n else\n {\n this.push(tween);\n }\n // update starting values for the next tween - if tweened values included 'p', then Tween\n // parsed that to add additional data that is required\n Object.assign(this._currentProps, tween.endProps);\n }\n\n /**\n * Add a single keyframe that doesn't tween.\n * Note that this has some capability to insert keyframes into the middle of a timeline, in order to\n * handle how masks are published, it should only be relied upon to add keyframes to the end of a timeline.\n * @param properties - The properties to set.\n * @param startFrame - The starting frame index.\n * @param duration - The number of frames to hold beyond startFrame (0 is single frame)\n */\n public addKeyframe(properties: TweenProps, startFrame: number, duration = 0): void\n {\n // see if we need to go back in and insert properties\n if (this.length && this[this.length - 1].startFrame >= startFrame)\n {\n for (let i = this.length - 1; i >= 0; --i)\n {\n const prev = this[i];\n\n // insert into an existing frame that shares the same keyframe\n if (prev.startFrame === startFrame)\n {\n // update the start props\n Object.assign(prev.startProps, properties);\n // carry the new props over unless they're already overridden by end props\n prev.endProps = Object.assign({}, prev.startProps, prev.endProps);\n // go through any later keyframes to update them the same way\n for (let k = i + 1; k < this.length; ++k)\n {\n const next = this[k];\n\n next.startProps = Object.assign({}, properties, next.startProps);\n next.endProps = Object.assign({}, next.startProps, next.endProps);\n }\n break;\n }\n // insert into the middle of an extended keyframe (but *not* one that tweens)\n else if (prev.startFrame < startFrame && prev.endFrame > startFrame && prev.isTweenlessFrame)\n {\n prev.endFrame = startFrame - 1;\n const startProps = Object.assign({}, prev.endProps, properties);\n // create the new Tween and add it to the list\n const tween = new Tween(this.target, startProps, null, startFrame, duration);\n\n this.splice(i, 0, tween);\n // go through any later keyframes to update them with our inserted props\n for (let k = i + 1; k < this.length; ++k)\n {\n const next = this[k];\n\n next.startProps = Object.assign({}, properties, next.startProps);\n next.endProps = Object.assign({}, next.startProps, next.endProps);\n }\n break;\n }\n // insert in a gap between frames (which shouldn't really happen, but just in case)\n else if (prev.endFrame < startFrame)\n {\n const startProps = Object.assign({}, prev.endProps, properties);\n // create the new Tween and add it to the list\n const tween = new Tween(this.target, startProps, null, startFrame, duration);\n\n this.splice(i, 0, tween);\n\n // go through any later keyframes to update them with our inserted props\n for (let k = i + 1; k < this.length; ++k)\n {\n const next = this[k];\n\n next.startProps = Object.assign({}, properties, next.startProps);\n next.endProps = Object.assign({}, next.startProps, next.endProps);\n }\n break;\n }\n }\n // save in current props, but don't take priority over existing values since we went back in time\n Object.assign(this._currentProps, properties, this._currentProps);\n }\n else\n {\n this.extendLastFrame(startFrame - 1);\n const startProps = Object.assign({}, this._currentProps, properties);\n // create the new Tween and add it to the list\n const tween = new Tween(this.target, startProps, null, startFrame, duration);\n\n this.push(tween);\n Object.assign(this._currentProps, tween.endProps);\n }\n }\n\n /**\n * Extend the last frame of the tween.\n * @param endFrame - The ending frame index.\n */\n public extendLastFrame(endFrame: number): void\n {\n if (this.length)\n {\n const prevTween = this[this.length - 1];\n\n if (prevTween.endFrame < endFrame)\n {\n if (prevTween.isTweenlessFrame)\n {\n prevTween.endFrame = endFrame;\n prevTween.duration = endFrame - prevTween.startFrame;\n }\n else\n {\n this.addKeyframe(\n this._currentProps,\n prevTween.endFrame + 1,\n endFrame - (prevTween.endFrame + 1),\n );\n }\n }\n }\n }\n\n /**\n * Get the value for a property\n * @param prop\n */\n private getPropFromShorthand<P extends keyof TweenProps>(prop: P): TweenProps[P]\n {\n const target = this.target;\n\n switch (prop)\n {\n case 'x':\n return target.position.x as any;\n case 'y':\n return target.position.y as any;\n case 'sx':\n return target.scale.x as any;\n case 'sy':\n return target.scale.y as any;\n case 'kx':\n return target.skew.x as any;\n case 'ky':\n return target.skew.y as any;\n case 'r':\n return target.rotation as any;\n case 'a':\n return target.alpha as any;\n case 'v':\n return target.visible as any;\n case 'm':\n return target.mask as any;\n // case 't':\n // return target.tint;\n // not sure if we'll actually handle graphics this way?\n // g: return null;\n }\n\n return null;\n }\n\n public destroy(): void\n {\n this._currentProps = null;\n this.length = 0;\n }\n}\n","import { ColorMatrixFilter } from '@pixi/filter-color-matrix';\nimport { Container } from '@pixi/display';\nimport { Graphics } from '@pixi/graphics';\nimport { Sprite } from '@pixi/sprite';\nimport { utils } from './utils';\n\n/**\n * Utility subclass of PIXI.Container\n */\nexport class AnimateContainer extends Container\n{\n // **************************\n // Container methods\n // **************************\n\n /**\n * Shortcut for `addChild`.\n */\n public ac = super.addChild;\n\n // **************************\n // DisplayObject methods\n // **************************\n\n /**\n * Function to set if this is renderable or not. Useful for setting masks.\n * @param renderable - Make renderable. Defaults to false.\n * @return This instance, for chaining.\n */\n public setRenderable(renderable?: boolean): this\n {\n this.renderable = !!renderable;\n\n return this;\n }\n /**\n * Shortcut for `setRenderable`.\n */\n public re = this.setRenderable;\n\n /**\n * Shortcut for `setTransform`.\n */\n public t = super.setTransform;\n\n /**\n * Setter for mask to be able to chain.\n * @param mask - The mask shape to use\n * @return Instance for chaining\n */\n public setMask(mask: Graphics | Sprite): this\n {\n // According to PIXI, only Graphics and Sprites can\n // be used as mask, let's ignore everything else, like other\n // movieclips and di