337 lines
6.6 KiB
JavaScript
337 lines
6.6 KiB
JavaScript
import { Ease } from './Controller.js'
|
|
import {
|
|
delimiter,
|
|
numberAndUnit,
|
|
isPathLetter
|
|
} from '../modules/core/regex.js'
|
|
import { extend } from '../utils/adopter.js'
|
|
import Color from '../types/Color.js'
|
|
import PathArray from '../types/PathArray.js'
|
|
import SVGArray from '../types/SVGArray.js'
|
|
import SVGNumber from '../types/SVGNumber.js'
|
|
|
|
const getClassForType = (value) => {
|
|
const type = typeof value
|
|
|
|
if (type === 'number') {
|
|
return SVGNumber
|
|
} else if (type === 'string') {
|
|
if (Color.isColor(value)) {
|
|
return Color
|
|
} else if (delimiter.test(value)) {
|
|
return isPathLetter.test(value) ? PathArray : SVGArray
|
|
} else if (numberAndUnit.test(value)) {
|
|
return SVGNumber
|
|
} else {
|
|
return NonMorphable
|
|
}
|
|
} else if (morphableTypes.indexOf(value.constructor) > -1) {
|
|
return value.constructor
|
|
} else if (Array.isArray(value)) {
|
|
return SVGArray
|
|
} else if (type === 'object') {
|
|
return ObjectBag
|
|
} else {
|
|
return NonMorphable
|
|
}
|
|
}
|
|
|
|
export default class Morphable {
|
|
constructor(stepper) {
|
|
this._stepper = stepper || new Ease('-')
|
|
|
|
this._from = null
|
|
this._to = null
|
|
this._type = null
|
|
this._context = null
|
|
this._morphObj = null
|
|
}
|
|
|
|
at(pos) {
|
|
return this._morphObj.morph(
|
|
this._from,
|
|
this._to,
|
|
pos,
|
|
this._stepper,
|
|
this._context
|
|
)
|
|
}
|
|
|
|
done() {
|
|
const complete = this._context.map(this._stepper.done).reduce(function (
|
|
last,
|
|
curr
|
|
) {
|
|
return last && curr
|
|
}, true)
|
|
return complete
|
|
}
|
|
|
|
from(val) {
|
|
if (val == null) {
|
|
return this._from
|
|
}
|
|
|
|
this._from = this._set(val)
|
|
return this
|
|
}
|
|
|
|
stepper(stepper) {
|
|
if (stepper == null) return this._stepper
|
|
this._stepper = stepper
|
|
return this
|
|
}
|
|
|
|
to(val) {
|
|
if (val == null) {
|
|
return this._to
|
|
}
|
|
|
|
this._to = this._set(val)
|
|
return this
|
|
}
|
|
|
|
type(type) {
|
|
// getter
|
|
if (type == null) {
|
|
return this._type
|
|
}
|
|
|
|
// setter
|
|
this._type = type
|
|
return this
|
|
}
|
|
|
|
_set(value) {
|
|
if (!this._type) {
|
|
this.type(getClassForType(value))
|
|
}
|
|
|
|
let result = new this._type(value)
|
|
if (this._type === Color) {
|
|
result = this._to
|
|
? result[this._to[4]]()
|
|
: this._from
|
|
? result[this._from[4]]()
|
|
: result
|
|
}
|
|
|
|
if (this._type === ObjectBag) {
|
|
result = this._to
|
|
? result.align(this._to)
|
|
: this._from
|
|
? result.align(this._from)
|
|
: result
|
|
}
|
|
|
|
result = result.toConsumable()
|
|
|
|
this._morphObj = this._morphObj || new this._type()
|
|
this._context =
|
|
this._context ||
|
|
Array.apply(null, Array(result.length))
|
|
.map(Object)
|
|
.map(function (o) {
|
|
o.done = true
|
|
return o
|
|
})
|
|
return result
|
|
}
|
|
}
|
|
|
|
export class NonMorphable {
|
|
constructor(...args) {
|
|
this.init(...args)
|
|
}
|
|
|
|
init(val) {
|
|
val = Array.isArray(val) ? val[0] : val
|
|
this.value = val
|
|
return this
|
|
}
|
|
|
|
toArray() {
|
|
return [this.value]
|
|
}
|
|
|
|
valueOf() {
|
|
return this.value
|
|
}
|
|
}
|
|
|
|
export class TransformBag {
|
|
constructor(...args) {
|
|
this.init(...args)
|
|
}
|
|
|
|
init(obj) {
|
|
if (Array.isArray(obj)) {
|
|
obj = {
|
|
scaleX: obj[0],
|
|
scaleY: obj[1],
|
|
shear: obj[2],
|
|
rotate: obj[3],
|
|
translateX: obj[4],
|
|
translateY: obj[5],
|
|
originX: obj[6],
|
|
originY: obj[7]
|
|
}
|
|
}
|
|
|
|
Object.assign(this, TransformBag.defaults, obj)
|
|
return this
|
|
}
|
|
|
|
toArray() {
|
|
const v = this
|
|
|
|
return [
|
|
v.scaleX,
|
|
v.scaleY,
|
|
v.shear,
|
|
v.rotate,
|
|
v.translateX,
|
|
v.translateY,
|
|
v.originX,
|
|
v.originY
|
|
]
|
|
}
|
|
}
|
|
|
|
TransformBag.defaults = {
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
shear: 0,
|
|
rotate: 0,
|
|
translateX: 0,
|
|
translateY: 0,
|
|
originX: 0,
|
|
originY: 0
|
|
}
|
|
|
|
const sortByKey = (a, b) => {
|
|
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0
|
|
}
|
|
|
|
export class ObjectBag {
|
|
constructor(...args) {
|
|
this.init(...args)
|
|
}
|
|
|
|
align(other) {
|
|
const values = this.values
|
|
for (let i = 0, il = values.length; i < il; ++i) {
|
|
// If the type is the same we only need to check if the color is in the correct format
|
|
if (values[i + 1] === other[i + 1]) {
|
|
if (values[i + 1] === Color && other[i + 7] !== values[i + 7]) {
|
|
const space = other[i + 7]
|
|
const color = new Color(this.values.splice(i + 3, 5))
|
|
[space]()
|
|
.toArray()
|
|
this.values.splice(i + 3, 0, ...color)
|
|
}
|
|
|
|
i += values[i + 2] + 2
|
|
continue
|
|
}
|
|
|
|
if (!other[i + 1]) {
|
|
return this
|
|
}
|
|
|
|
// The types differ, so we overwrite the new type with the old one
|
|
// And initialize it with the types default (e.g. black for color or 0 for number)
|
|
const defaultObject = new other[i + 1]().toArray()
|
|
|
|
// Than we fix the values array
|
|
const toDelete = values[i + 2] + 3
|
|
|
|
values.splice(
|
|
i,
|
|
toDelete,
|
|
other[i],
|
|
other[i + 1],
|
|
other[i + 2],
|
|
...defaultObject
|
|
)
|
|
|
|
i += values[i + 2] + 2
|
|
}
|
|
return this
|
|
}
|
|
|
|
init(objOrArr) {
|
|
this.values = []
|
|
|
|
if (Array.isArray(objOrArr)) {
|
|
this.values = objOrArr.slice()
|
|
return
|
|
}
|
|
|
|
objOrArr = objOrArr || {}
|
|
const entries = []
|
|
|
|
for (const i in objOrArr) {
|
|
const Type = getClassForType(objOrArr[i])
|
|
const val = new Type(objOrArr[i]).toArray()
|
|
entries.push([i, Type, val.length, ...val])
|
|
}
|
|
|
|
entries.sort(sortByKey)
|
|
|
|
this.values = entries.reduce((last, curr) => last.concat(curr), [])
|
|
return this
|
|
}
|
|
|
|
toArray() {
|
|
return this.values
|
|
}
|
|
|
|
valueOf() {
|
|
const obj = {}
|
|
const arr = this.values
|
|
|
|
// for (var i = 0, len = arr.length; i < len; i += 2) {
|
|
while (arr.length) {
|
|
const key = arr.shift()
|
|
const Type = arr.shift()
|
|
const num = arr.shift()
|
|
const values = arr.splice(0, num)
|
|
obj[key] = new Type(values) // .valueOf()
|
|
}
|
|
|
|
return obj
|
|
}
|
|
}
|
|
|
|
const morphableTypes = [NonMorphable, TransformBag, ObjectBag]
|
|
|
|
export function registerMorphableType(type = []) {
|
|
morphableTypes.push(...[].concat(type))
|
|
}
|
|
|
|
export function makeMorphable() {
|
|
extend(morphableTypes, {
|
|
to(val) {
|
|
return new Morphable()
|
|
.type(this.constructor)
|
|
.from(this.toArray()) // this.valueOf())
|
|
.to(val)
|
|
},
|
|
fromArray(arr) {
|
|
this.init(arr)
|
|
return this
|
|
},
|
|
toConsumable() {
|
|
return this.toArray()
|
|
},
|
|
morph(from, to, pos, stepper, context) {
|
|
const mapper = function (i, index) {
|
|
return stepper.step(i, to[index], pos, context[index], context)
|
|
}
|
|
|
|
return this.fromArray(from.map(mapper))
|
|
}
|
|
})
|
|
}
|