feat: smart animation mode
This commit is contained in:
parent
7534a613cb
commit
1114828000
11 changed files with 207 additions and 44 deletions
1
src/compilers/Configuration.ts
Normal file
1
src/compilers/Configuration.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const isBuildMode = process.argv.includes("--build")
|
||||
|
|
@ -4,6 +4,7 @@ import { UI } from "../components/UI.js"
|
|||
import { AnimType } from "../types/enums/AnimType.js"
|
||||
import { Renderer } from "../types/enums/Renderer.js"
|
||||
import { Type } from "../types/enums/Type.js"
|
||||
import { isBuildMode } from "./Configuration.js"
|
||||
|
||||
type Element = UI<Type, Renderer | null> | AnimationKeyframe<AnimType>
|
||||
interface FileInterface {
|
||||
|
|
@ -16,6 +17,7 @@ export class Memory extends Class {
|
|||
protected static files: Files = new Map<string, FileInterface>()
|
||||
|
||||
public static add(element: UI<Type, Renderer | null> | AnimationKeyframe<AnimType>) {
|
||||
if (!isBuildMode) return
|
||||
let file = Memory.files.get(element.path)
|
||||
|
||||
if (!file) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { isBuildMode } from "./Configuration.js"
|
||||
import { Memory } from "./Memory.js"
|
||||
|
||||
const isBuildMode = process.argv.includes("--build")
|
||||
|
||||
if (isBuildMode) {
|
||||
process.on("beforeExit", () => {
|
||||
console.log(JSON.stringify(Memory.build(), null, 2))
|
||||
|
|
|
|||
|
|
@ -1,19 +1,33 @@
|
|||
import { AnimType } from "../types/enums/AnimType.js"
|
||||
import { SmartAnimation } from "../types/enums/SmartAnimation.js"
|
||||
import { AnimationProperties } from "../types/properties/element/Animation.js"
|
||||
import { Class } from "./Class.js"
|
||||
import { KeyframeController } from "./KeyframeController.js"
|
||||
|
||||
import util from "node:util"
|
||||
|
||||
type Anim<T extends AnimType> = AnimationProperties<T> | number
|
||||
type AnimWithSmartAnimation<T extends AnimType> = [SmartAnimation | Anim<T>, ...Anim<T>[]]
|
||||
|
||||
export class Animation<T extends AnimType> extends Class {
|
||||
protected keyframes: KeyframeController<AnimType>[] = []
|
||||
protected loop = false
|
||||
private smartAnimationMode: SmartAnimation = "none"
|
||||
|
||||
constructor(
|
||||
readonly type: T,
|
||||
...keyframes: (AnimationProperties<T> | number)[]
|
||||
...keyframes: AnimWithSmartAnimation<T>
|
||||
) {
|
||||
super()
|
||||
if (type === AnimType.WAIT) console.warn("Why are you create a wait animation?")
|
||||
this.addKeyframes(...keyframes)
|
||||
|
||||
if ([AnimType.ASEPRITE_FLIP_BOOK, AnimType.FLIP_BOOK, AnimType.WAIT].includes(type)) {
|
||||
throw new Error(`${type} is not need for Animation constructor, please use AnimetionKeyframe instead!`)
|
||||
}
|
||||
|
||||
if (typeof keyframes[0] === "string") {
|
||||
this.smartAnimationMode = keyframes[0]
|
||||
this.addKeyframes(...(keyframes.slice(1) as Anim<T>[]))
|
||||
} else this.addKeyframes(...(keyframes as Anim<T>[]))
|
||||
}
|
||||
|
||||
protected lastKey() {
|
||||
|
|
@ -24,27 +38,90 @@ export class Animation<T extends AnimType> extends Class {
|
|||
return this.keyframes[0]
|
||||
}
|
||||
|
||||
addKeyframes(...keyframes: (AnimationProperties<T> | number)[]) {
|
||||
for (const $ of keyframes) {
|
||||
let keyframe: AnimationProperties<AnimType>, animType: AnimType
|
||||
|
||||
if (typeof $ === "number") {
|
||||
keyframe = { duration: $ }
|
||||
animType = AnimType.WAIT
|
||||
} else {
|
||||
keyframe = $
|
||||
animType = this.type
|
||||
at(index: number) {
|
||||
const frame = this.keyframes[index]
|
||||
if (frame) return frame
|
||||
else throw new Error(`No frame at index ${index}`)
|
||||
}
|
||||
|
||||
const keyframeController = new KeyframeController(animType, keyframe as AnimationProperties<T>)
|
||||
private transformKeyframe(keyframe: Anim<T>) {
|
||||
if (typeof keyframe === "number") {
|
||||
return { type: AnimType.WAIT, properties: <AnimationProperties<T>>(<unknown>{ duration: keyframe }) }
|
||||
} else {
|
||||
return { type: this.type, properties: keyframe }
|
||||
}
|
||||
}
|
||||
|
||||
private addKeyframes(...keyframes: Anim<T>[]) {
|
||||
if (this.smartAnimationMode === "none") {
|
||||
for (const $ of keyframes) {
|
||||
const { type, properties } = this.transformKeyframe($)
|
||||
const keyframeController = new KeyframeController(type, properties)
|
||||
const prevKeyframe = this.lastKey()
|
||||
if (prevKeyframe) prevKeyframe.setNext(keyframeController)
|
||||
|
||||
if (this.loop) keyframeController.setNext(this.firstKey() as KeyframeController<T>)
|
||||
|
||||
this.keyframes.push(keyframeController)
|
||||
}
|
||||
} else if (this.smartAnimationMode === "frame") {
|
||||
let lastDuration = 0
|
||||
|
||||
for (const $ of keyframes) {
|
||||
const { type, properties } = <{ type: AnimType; properties: { from?: unknown; to?: unknown } }>(
|
||||
(<unknown>this.transformKeyframe($))
|
||||
)
|
||||
|
||||
if ((<{ duration?: number }>(<unknown>properties)).duration !== undefined)
|
||||
lastDuration = (<{ duration?: number }>(<unknown>properties)).duration || 0
|
||||
;(<{ duration?: number }>(<unknown>properties)).duration = lastDuration
|
||||
|
||||
if (type !== AnimType.WAIT) {
|
||||
const { from, to } = properties
|
||||
if (from === undefined) properties.from = to
|
||||
else if (to === undefined) properties.to = from
|
||||
}
|
||||
|
||||
const keyframeController = new KeyframeController(type, <AnimationProperties<T>>properties)
|
||||
const prevKeyframe = this.lastKey()
|
||||
if (prevKeyframe) prevKeyframe.setNext(keyframeController)
|
||||
this.keyframes.push(keyframeController)
|
||||
}
|
||||
} else if (this.smartAnimationMode === "smooth" || this.smartAnimationMode === "smooth_loop") {
|
||||
let lastDuration = 0,
|
||||
lastTo
|
||||
|
||||
for (const $ of keyframes) {
|
||||
const { type, properties } = <{ type: AnimType; properties: { from?: unknown; to?: unknown } }>(
|
||||
(<unknown>this.transformKeyframe($))
|
||||
)
|
||||
|
||||
if (properties.to === undefined && type !== AnimType.WAIT)
|
||||
throw new Error(`To property is required in smooth mode`)
|
||||
|
||||
if ((<{ duration?: number }>(<unknown>properties)).duration !== undefined)
|
||||
lastDuration = (<{ duration?: number }>(<unknown>properties)).duration || 0
|
||||
;(<{ duration?: number }>(<unknown>properties)).duration = lastDuration
|
||||
|
||||
if (type !== AnimType.WAIT) {
|
||||
if (properties.to) {
|
||||
if (properties.from === undefined) properties.from = lastTo
|
||||
lastTo = properties.to
|
||||
}
|
||||
}
|
||||
|
||||
const keyframeController = new KeyframeController(type, <AnimationProperties<T>>properties)
|
||||
const prevKeyframe = this.lastKey()
|
||||
if (prevKeyframe) prevKeyframe.setNext(keyframeController)
|
||||
this.keyframes.push(keyframeController)
|
||||
}
|
||||
|
||||
if (this.smartAnimationMode === "smooth_loop") {
|
||||
const firstKey = <{ properties: { from?: unknown } }>(<unknown>this.firstKey())
|
||||
if (firstKey.properties.from === undefined && lastTo !== undefined) {
|
||||
firstKey.properties.from = lastTo
|
||||
}
|
||||
}
|
||||
} else throw new Error(`Unknown smart animation mode: ${this.smartAnimationMode}`)
|
||||
|
||||
if (this.loop) this.lastKey().setNext(this.firstKey())
|
||||
}
|
||||
|
||||
setLoop(boolean: boolean) {
|
||||
|
|
@ -60,4 +137,19 @@ export class Animation<T extends AnimType> extends Class {
|
|||
|
||||
return this
|
||||
}
|
||||
|
||||
protected toString() {
|
||||
return String(this.firstKey())
|
||||
}
|
||||
|
||||
protected [util.inspect.custom]($: any, opts: any) {
|
||||
const out = this.keyframes.map((v, i) => {
|
||||
return ` \x1b[33m${i}\x1b[0m: \x1b[33mKeyframe\x1b[0m<\x1b[92m${v.type}\x1b[0m> \x1b[92m"${v}\x1b[92m"\x1b[0m ${util.inspect(v.serialize(), opts)}`.replace(
|
||||
/\n/g,
|
||||
"\n ",
|
||||
)
|
||||
})
|
||||
|
||||
return `\x1b[33mAnimation\x1b[0m<\x1b[92m${this.type}\x1b[0m> \x1b[92m"${this}\x1b[92m"\x1b[0m {\n${out.join("\n")}\n}\n`
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { FormatAnimationProperties } from "../compilers/FormatProperties.js"
|
|||
import { Memory } from "../compilers/Memory.js"
|
||||
import { AnimType } from "../types/enums/AnimType.js"
|
||||
import { KeyframeAnimationProperties } from "../types/properties/element/Animation.js"
|
||||
import { Animation } from "./Animation.js"
|
||||
import { Class } from "./Class.js"
|
||||
import { RandomString } from "./Utils.js"
|
||||
|
||||
|
|
@ -12,6 +13,8 @@ export class AnimationKeyframe<T extends AnimType> extends Class {
|
|||
readonly name: string
|
||||
readonly namespace: string
|
||||
|
||||
readonly extend?: AnimationKeyframe<T> | Animation<T>
|
||||
|
||||
constructor(
|
||||
readonly type: T,
|
||||
readonly properties: KeyframeAnimationProperties<T>,
|
||||
|
|
@ -38,16 +41,30 @@ export class AnimationKeyframe<T extends AnimType> extends Class {
|
|||
Memory.add(this)
|
||||
}
|
||||
|
||||
setNext(keyframe: AnimationKeyframe<AnimType>) {
|
||||
this.properties.next = keyframe
|
||||
return this
|
||||
}
|
||||
|
||||
clearNext() {
|
||||
delete this.properties.next
|
||||
return this
|
||||
}
|
||||
|
||||
protected toJsonUI() {
|
||||
return FormatAnimationProperties(this.properties)
|
||||
}
|
||||
|
||||
protected toJSON() {
|
||||
if (this.extend) {
|
||||
return this.toJsonUI()
|
||||
} else {
|
||||
return {
|
||||
anim_type: this.type,
|
||||
...this.toJsonUI(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected toString() {
|
||||
return `@${this.namespace}.${this.name}`
|
||||
|
|
|
|||
|
|
@ -7,13 +7,7 @@ export class KeyframeController<T extends AnimType> extends AnimationKeyframe<T>
|
|||
super(type, properties, name, namespace, path)
|
||||
}
|
||||
|
||||
setNext(keyframe: AnimationKeyframe<AnimType>) {
|
||||
this.properties.next = keyframe
|
||||
return this
|
||||
}
|
||||
|
||||
clearNext() {
|
||||
delete this.properties.next
|
||||
return this
|
||||
serialize() {
|
||||
return this.toJsonUI()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,9 @@ import { Parser } from "../compilers/bindings/Parser.js"
|
|||
import { BindingType } from "../types/enums/BindingType.js"
|
||||
import { AnimType } from "../types/enums/AnimType.js"
|
||||
import { AnimationKeyframe } from "./AnimationKeyframe.js"
|
||||
import { KeyframeAnimationProperties } from "../types/properties/element/Animation.js"
|
||||
import { AnimationProperties, KeyframeAnimationProperties } from "../types/properties/element/Animation.js"
|
||||
import { Animation } from "./Animation.js"
|
||||
import { SmartAnimation } from "../types/enums/SmartAnimation.js"
|
||||
|
||||
const CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
type CompileBinding = `[${string}]`
|
||||
|
|
@ -315,3 +317,44 @@ export function KeyframeAsepriteFlipBook(
|
|||
) {
|
||||
return new AnimationKeyframe(AnimType.ASEPRITE_FLIP_BOOK, properties || {}, name, namespace)
|
||||
}
|
||||
|
||||
// Quick Animation
|
||||
type Anim<T extends AnimType> = AnimationProperties<T> | number
|
||||
type AnimWithSmartAnim<T extends AnimType> = [SmartAnimation | Anim<T>, ...Anim<T>[]]
|
||||
|
||||
export function AnimationOffset(...keyframes: AnimWithSmartAnim<AnimType.OFFSET>) {
|
||||
return new Animation(AnimType.OFFSET, ...keyframes)
|
||||
}
|
||||
|
||||
export function AnimationSize(...keyframes: AnimWithSmartAnim<AnimType.SIZE>) {
|
||||
return new Animation(AnimType.SIZE, ...keyframes)
|
||||
}
|
||||
|
||||
export function AnimationUV(...keyframes: AnimWithSmartAnim<AnimType.UV>) {
|
||||
return new Animation(AnimType.UV, ...keyframes)
|
||||
}
|
||||
|
||||
export function AnimationClip(...keyframes: AnimWithSmartAnim<AnimType.CLIP>) {
|
||||
return new Animation(AnimType.CLIP, ...keyframes)
|
||||
}
|
||||
|
||||
export function AnimationColor(...keyframes: AnimWithSmartAnim<AnimType.COLOR>) {
|
||||
return new Animation(AnimType.COLOR, ...keyframes)
|
||||
}
|
||||
|
||||
export function AnimationAlpha(...keyframes: AnimWithSmartAnim<AnimType.ALPHA>) {
|
||||
return new Animation(AnimType.ALPHA, ...keyframes)
|
||||
}
|
||||
|
||||
// Animation Extendof
|
||||
export function AnimationExtendsOf<T extends AnimType>(
|
||||
animation: AnimationKeyframe<T> | Animation<T>,
|
||||
properties?: AnimationProperties<T>,
|
||||
) {
|
||||
const anim = new AnimationKeyframe(animation.type, properties || {})
|
||||
|
||||
// @ts-ignore
|
||||
anim.extend = animation
|
||||
|
||||
return anim
|
||||
}
|
||||
|
|
|
|||
1
src/types/enums/SmartAnimation.ts
Normal file
1
src/types/enums/SmartAnimation.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export type SmartAnimation = "none" | "frame" | "smooth" | "smooth_loop"
|
||||
|
|
@ -28,3 +28,4 @@ export { SliderName } from "./SliderName.js"
|
|||
export { ToggleName } from "./ToggleName.js"
|
||||
export { BagBinding } from "./BagBinding.js"
|
||||
export { Binding } from "./Binding.js"
|
||||
export { SmartAnimation } from "./SmartAnimation.js"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { AnimationKeyframe } from "../../../components/AnimationKeyframe.js"
|
||||
import { AnimType } from "../../enums/AnimType.js"
|
||||
import { Easing } from "../../enums/Easing.js"
|
||||
import { Array2, Array3, Value } from "../value.js"
|
||||
import { Animation, Array2, Array3, Value } from "../value.js"
|
||||
import * as components from "../../../components/Animation.js"
|
||||
|
||||
export interface DurationAnimation {
|
||||
duration?: Value<number>
|
||||
|
|
@ -65,7 +66,7 @@ export interface AnimationPropertiesItem {
|
|||
}
|
||||
|
||||
export interface KeyframeAnimationPropertiesItem extends AnimationPropertiesItem {
|
||||
next?: Value<string | AnimationKeyframe<AnimType>>
|
||||
next?: Value<string | AnimationKeyframe<AnimType> | components.Animation<AnimType>>
|
||||
}
|
||||
|
||||
export type KeyframeAnimationProperties<T extends keyof AnimationValueType> = Partial<AnimationValueType[T]> &
|
||||
|
|
|
|||
26
test/app.ts
26
test/app.ts
|
|
@ -1,10 +1,22 @@
|
|||
import { Animation, AnimType } from ".."
|
||||
import { AnimationSize } from ".."
|
||||
|
||||
const animation = new Animation(
|
||||
AnimType.OFFSET,
|
||||
const animation = AnimationSize(
|
||||
"smooth_loop",
|
||||
{
|
||||
from: [0, 0],
|
||||
to: [100, 100],
|
||||
to: [10, 10],
|
||||
duration: 1.5,
|
||||
},
|
||||
123,
|
||||
).setLoop(false)
|
||||
{
|
||||
to: [1, 1],
|
||||
},
|
||||
1,
|
||||
{
|
||||
from: [10, 10],
|
||||
to: [20, 20],
|
||||
},
|
||||
{
|
||||
to: [1, 1],
|
||||
},
|
||||
).setLoop(true)
|
||||
|
||||
console.log(animation)
|
||||
|
|
|
|||
Reference in a new issue