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 { AnimType } from "../types/enums/AnimType.js"
|
||||||
import { Renderer } from "../types/enums/Renderer.js"
|
import { Renderer } from "../types/enums/Renderer.js"
|
||||||
import { Type } from "../types/enums/Type.js"
|
import { Type } from "../types/enums/Type.js"
|
||||||
|
import { isBuildMode } from "./Configuration.js"
|
||||||
|
|
||||||
type Element = UI<Type, Renderer | null> | AnimationKeyframe<AnimType>
|
type Element = UI<Type, Renderer | null> | AnimationKeyframe<AnimType>
|
||||||
interface FileInterface {
|
interface FileInterface {
|
||||||
|
|
@ -16,6 +17,7 @@ export class Memory extends Class {
|
||||||
protected static files: Files = new Map<string, FileInterface>()
|
protected static files: Files = new Map<string, FileInterface>()
|
||||||
|
|
||||||
public static add(element: UI<Type, Renderer | null> | AnimationKeyframe<AnimType>) {
|
public static add(element: UI<Type, Renderer | null> | AnimationKeyframe<AnimType>) {
|
||||||
|
if (!isBuildMode) return
|
||||||
let file = Memory.files.get(element.path)
|
let file = Memory.files.get(element.path)
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
import { isBuildMode } from "./Configuration.js"
|
||||||
import { Memory } from "./Memory.js"
|
import { Memory } from "./Memory.js"
|
||||||
|
|
||||||
const isBuildMode = process.argv.includes("--build")
|
|
||||||
|
|
||||||
if (isBuildMode) {
|
if (isBuildMode) {
|
||||||
process.on("beforeExit", () => {
|
process.on("beforeExit", () => {
|
||||||
console.log(JSON.stringify(Memory.build(), null, 2))
|
console.log(JSON.stringify(Memory.build(), null, 2))
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,33 @@
|
||||||
import { AnimType } from "../types/enums/AnimType.js"
|
import { AnimType } from "../types/enums/AnimType.js"
|
||||||
|
import { SmartAnimation } from "../types/enums/SmartAnimation.js"
|
||||||
import { AnimationProperties } from "../types/properties/element/Animation.js"
|
import { AnimationProperties } from "../types/properties/element/Animation.js"
|
||||||
import { Class } from "./Class.js"
|
import { Class } from "./Class.js"
|
||||||
import { KeyframeController } from "./KeyframeController.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 {
|
export class Animation<T extends AnimType> extends Class {
|
||||||
protected keyframes: KeyframeController<AnimType>[] = []
|
protected keyframes: KeyframeController<AnimType>[] = []
|
||||||
protected loop = false
|
protected loop = false
|
||||||
|
private smartAnimationMode: SmartAnimation = "none"
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly type: T,
|
readonly type: T,
|
||||||
...keyframes: (AnimationProperties<T> | number)[]
|
...keyframes: AnimWithSmartAnimation<T>
|
||||||
) {
|
) {
|
||||||
super()
|
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() {
|
protected lastKey() {
|
||||||
|
|
@ -24,27 +38,90 @@ export class Animation<T extends AnimType> extends Class {
|
||||||
return this.keyframes[0]
|
return this.keyframes[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
addKeyframes(...keyframes: (AnimationProperties<T> | number)[]) {
|
at(index: number) {
|
||||||
for (const $ of keyframes) {
|
const frame = this.keyframes[index]
|
||||||
let keyframe: AnimationProperties<AnimType>, animType: AnimType
|
if (frame) return frame
|
||||||
|
else throw new Error(`No frame at index ${index}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof $ === "number") {
|
private transformKeyframe(keyframe: Anim<T>) {
|
||||||
keyframe = { duration: $ }
|
if (typeof keyframe === "number") {
|
||||||
animType = AnimType.WAIT
|
return { type: AnimType.WAIT, properties: <AnimationProperties<T>>(<unknown>{ duration: keyframe }) }
|
||||||
} else {
|
} else {
|
||||||
keyframe = $
|
return { type: this.type, properties: keyframe }
|
||||||
animType = this.type
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyframeController = new KeyframeController(animType, keyframe as AnimationProperties<T>)
|
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}`)
|
||||||
|
|
||||||
const prevKeyframe = this.lastKey()
|
if (this.loop) this.lastKey().setNext(this.firstKey())
|
||||||
if (prevKeyframe) prevKeyframe.setNext(keyframeController)
|
|
||||||
|
|
||||||
if (this.loop) keyframeController.setNext(this.firstKey() as KeyframeController<T>)
|
|
||||||
|
|
||||||
this.keyframes.push(keyframeController)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoop(boolean: boolean) {
|
setLoop(boolean: boolean) {
|
||||||
|
|
@ -60,4 +137,19 @@ export class Animation<T extends AnimType> extends Class {
|
||||||
|
|
||||||
return this
|
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 { Memory } from "../compilers/Memory.js"
|
||||||
import { AnimType } from "../types/enums/AnimType.js"
|
import { AnimType } from "../types/enums/AnimType.js"
|
||||||
import { KeyframeAnimationProperties } from "../types/properties/element/Animation.js"
|
import { KeyframeAnimationProperties } from "../types/properties/element/Animation.js"
|
||||||
|
import { Animation } from "./Animation.js"
|
||||||
import { Class } from "./Class.js"
|
import { Class } from "./Class.js"
|
||||||
import { RandomString } from "./Utils.js"
|
import { RandomString } from "./Utils.js"
|
||||||
|
|
||||||
|
|
@ -12,6 +13,8 @@ export class AnimationKeyframe<T extends AnimType> extends Class {
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly namespace: string
|
readonly namespace: string
|
||||||
|
|
||||||
|
readonly extend?: AnimationKeyframe<T> | Animation<T>
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly type: T,
|
readonly type: T,
|
||||||
readonly properties: KeyframeAnimationProperties<T>,
|
readonly properties: KeyframeAnimationProperties<T>,
|
||||||
|
|
@ -38,14 +41,28 @@ export class AnimationKeyframe<T extends AnimType> extends Class {
|
||||||
Memory.add(this)
|
Memory.add(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setNext(keyframe: AnimationKeyframe<AnimType>) {
|
||||||
|
this.properties.next = keyframe
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
clearNext() {
|
||||||
|
delete this.properties.next
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
protected toJsonUI() {
|
protected toJsonUI() {
|
||||||
return FormatAnimationProperties(this.properties)
|
return FormatAnimationProperties(this.properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toJSON() {
|
protected toJSON() {
|
||||||
return {
|
if (this.extend) {
|
||||||
anim_type: this.type,
|
return this.toJsonUI()
|
||||||
...this.toJsonUI(),
|
} else {
|
||||||
|
return {
|
||||||
|
anim_type: this.type,
|
||||||
|
...this.toJsonUI(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,7 @@ export class KeyframeController<T extends AnimType> extends AnimationKeyframe<T>
|
||||||
super(type, properties, name, namespace, path)
|
super(type, properties, name, namespace, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
setNext(keyframe: AnimationKeyframe<AnimType>) {
|
serialize() {
|
||||||
this.properties.next = keyframe
|
return this.toJsonUI()
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
clearNext() {
|
|
||||||
delete this.properties.next
|
|
||||||
return this
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ import { Parser } from "../compilers/bindings/Parser.js"
|
||||||
import { BindingType } from "../types/enums/BindingType.js"
|
import { BindingType } from "../types/enums/BindingType.js"
|
||||||
import { AnimType } from "../types/enums/AnimType.js"
|
import { AnimType } from "../types/enums/AnimType.js"
|
||||||
import { AnimationKeyframe } from "./AnimationKeyframe.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"
|
const CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||||
type CompileBinding = `[${string}]`
|
type CompileBinding = `[${string}]`
|
||||||
|
|
@ -315,3 +317,44 @@ export function KeyframeAsepriteFlipBook(
|
||||||
) {
|
) {
|
||||||
return new AnimationKeyframe(AnimType.ASEPRITE_FLIP_BOOK, properties || {}, name, namespace)
|
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"
|
||||||
|
|
@ -27,4 +27,5 @@ export { TextboxName } from "./TextboxName.js"
|
||||||
export { SliderName } from "./SliderName.js"
|
export { SliderName } from "./SliderName.js"
|
||||||
export { ToggleName } from "./ToggleName.js"
|
export { ToggleName } from "./ToggleName.js"
|
||||||
export { BagBinding } from "./BagBinding.js"
|
export { BagBinding } from "./BagBinding.js"
|
||||||
export { Binding } from "./Binding.js"
|
export { Binding } from "./Binding.js"
|
||||||
|
export { SmartAnimation } from "./SmartAnimation.js"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { AnimationKeyframe } from "../../../components/AnimationKeyframe.js"
|
import { AnimationKeyframe } from "../../../components/AnimationKeyframe.js"
|
||||||
import { AnimType } from "../../enums/AnimType.js"
|
import { AnimType } from "../../enums/AnimType.js"
|
||||||
import { Easing } from "../../enums/Easing.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 {
|
export interface DurationAnimation {
|
||||||
duration?: Value<number>
|
duration?: Value<number>
|
||||||
|
|
@ -65,7 +66,7 @@ export interface AnimationPropertiesItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyframeAnimationPropertiesItem extends 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]> &
|
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(
|
const animation = AnimationSize(
|
||||||
AnimType.OFFSET,
|
"smooth_loop",
|
||||||
{
|
{
|
||||||
from: [0, 0],
|
to: [10, 10],
|
||||||
to: [100, 100],
|
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