bak
This commit is contained in:
1 parent
c6863f2bfc
commit
94e9846c13
11 files changed
+985
-136
No files matched your search
+200
-33
@@ -16,6 +16,80 @@ interface State {
|
||||
playing: boolean
|
||||
}
|
||||
|
||||
function allKeyframes(layer: Layer): Keyframe[] {
|
||||
const out: Keyframe[] = [...layer.keyframes]
|
||||
if (layer.positionKeyframes) out.push(...layer.positionKeyframes)
|
||||
if (layer.rotationKeyframes) out.push(...layer.rotationKeyframes)
|
||||
return out
|
||||
}
|
||||
|
||||
function findKf(
|
||||
layer: Layer,
|
||||
keyframeId: string,
|
||||
): { kf: Keyframe; arr: Keyframe[] } | null {
|
||||
for (const arr of [layer.keyframes, layer.positionKeyframes, layer.rotationKeyframes]) {
|
||||
if (!arr) continue
|
||||
const kf = arr.find((k) => k.id === keyframeId)
|
||||
if (kf) return { kf, arr }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function sortKfs(arr: Keyframe[]) {
|
||||
arr.sort((a, b) => a.time - b.time)
|
||||
}
|
||||
|
||||
function upsertInto(
|
||||
arr: Keyframe[],
|
||||
time: number,
|
||||
props: Partial<AnimatableProps>,
|
||||
snapshotProps: Partial<AnimatableProps> | null,
|
||||
) {
|
||||
const existing = arr.find((k) => Math.abs(k.time - time) < KEYFRAME_EPSILON)
|
||||
if (existing) {
|
||||
existing.props = { ...existing.props, ...props }
|
||||
return
|
||||
}
|
||||
const kf: Keyframe = {
|
||||
id: uid(),
|
||||
time,
|
||||
props: snapshotProps ? { ...snapshotProps, ...props } : { ...props },
|
||||
easing: 'linear',
|
||||
}
|
||||
arr.push(kf)
|
||||
sortKfs(arr)
|
||||
}
|
||||
|
||||
const POSITION_KEYS = new Set(['x', 'y'])
|
||||
const ROTATION_KEYS = new Set(['rotation'])
|
||||
|
||||
function pickProps(
|
||||
src: Partial<AnimatableProps>,
|
||||
keys: Set<string>,
|
||||
): Partial<AnimatableProps> {
|
||||
const out: Partial<AnimatableProps> = {}
|
||||
for (const [k, v] of Object.entries(src)) {
|
||||
if (keys.has(k) && v !== undefined) (out as Record<string, unknown>)[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function omitProps(
|
||||
src: Partial<AnimatableProps>,
|
||||
keys: Set<string>,
|
||||
): Partial<AnimatableProps> {
|
||||
const out: Partial<AnimatableProps> = {}
|
||||
for (const [k, v] of Object.entries(src)) {
|
||||
if (!keys.has(k) && v !== undefined) (out as Record<string, unknown>)[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function hasKeys(obj: Partial<AnimatableProps>): boolean {
|
||||
for (const _ in obj) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export const useSceneStore = defineStore('scene', {
|
||||
state: (): State => ({
|
||||
scene: {
|
||||
@@ -34,7 +108,7 @@ export const useSceneStore = defineStore('scene', {
|
||||
lastKeyframeTime(state): number {
|
||||
let max = 0
|
||||
for (const layer of state.scene.layers) {
|
||||
for (const kf of layer.keyframes) {
|
||||
for (const kf of allKeyframes(layer)) {
|
||||
if (kf.time > max) max = kf.time
|
||||
}
|
||||
}
|
||||
@@ -43,7 +117,7 @@ export const useSceneStore = defineStore('scene', {
|
||||
effectiveEnd(state): number {
|
||||
let max = 0
|
||||
for (const layer of state.scene.layers) {
|
||||
for (const kf of layer.keyframes) {
|
||||
for (const kf of allKeyframes(layer)) {
|
||||
if (kf.time > max) max = kf.time
|
||||
}
|
||||
}
|
||||
@@ -80,9 +154,14 @@ export const useSceneStore = defineStore('scene', {
|
||||
},
|
||||
],
|
||||
}
|
||||
if (asset && (asset.kind === 'rect' || asset.kind === 'image')) {
|
||||
layer.offsetX = asset.width / 2
|
||||
layer.offsetY = asset.height / 2
|
||||
if (asset) {
|
||||
if (asset.kind === 'rect' || asset.kind === 'image') {
|
||||
layer.offsetX = asset.width / 2
|
||||
layer.offsetY = asset.height / 2
|
||||
} else if (asset.kind === 'circle' || asset.kind === 'text') {
|
||||
layer.offsetX = 0
|
||||
layer.offsetY = 0
|
||||
}
|
||||
}
|
||||
this.scene.layers.push(layer)
|
||||
this.selectedLayerId = layer.id
|
||||
@@ -115,39 +194,56 @@ export const useSceneStore = defineStore('scene', {
|
||||
upsertKeyframe(layerId: string, time: number, props: Partial<AnimatableProps>) {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
const existing = layer.keyframes.find((k) => Math.abs(k.time - time) < KEYFRAME_EPSILON)
|
||||
if (existing) {
|
||||
existing.props = { ...existing.props, ...props }
|
||||
if (!layer.separated) {
|
||||
const snapshot = sampleLayer(layer, time)
|
||||
upsertInto(layer.keyframes, time, props, snapshot)
|
||||
return
|
||||
}
|
||||
const snapshot = sampleLayer(layer, time)
|
||||
const kf: Keyframe = {
|
||||
id: uid(),
|
||||
time,
|
||||
props: { ...snapshot, ...props },
|
||||
easing: 'linear',
|
||||
const posProps = pickProps(props, POSITION_KEYS)
|
||||
const rotProps = pickProps(props, ROTATION_KEYS)
|
||||
const otherProps = omitProps(props, new Set(['x', 'y', 'rotation']))
|
||||
if (hasKeys(posProps)) {
|
||||
const arr = (layer.positionKeyframes ||= [])
|
||||
upsertInto(arr, time, posProps, null)
|
||||
}
|
||||
if (hasKeys(rotProps)) {
|
||||
const arr = (layer.rotationKeyframes ||= [])
|
||||
upsertInto(arr, time, rotProps, null)
|
||||
}
|
||||
if (hasKeys(otherProps)) {
|
||||
upsertInto(layer.keyframes, time, otherProps, null)
|
||||
}
|
||||
layer.keyframes.push(kf)
|
||||
layer.keyframes.sort((a, b) => a.time - b.time)
|
||||
},
|
||||
removeKeyframeAt(layerId: string, time: number) {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
layer.keyframes = layer.keyframes.filter((k) => Math.abs(k.time - time) >= KEYFRAME_EPSILON)
|
||||
const filter = (kfs?: Keyframe[]) =>
|
||||
kfs ? kfs.filter((k) => Math.abs(k.time - time) >= KEYFRAME_EPSILON) : kfs
|
||||
layer.keyframes = filter(layer.keyframes) ?? []
|
||||
if (layer.positionKeyframes) layer.positionKeyframes = filter(layer.positionKeyframes)
|
||||
if (layer.rotationKeyframes) layer.rotationKeyframes = filter(layer.rotationKeyframes)
|
||||
},
|
||||
setKeyframeEasing(layerId: string, time: number, easing: Easing) {
|
||||
removeKeyframe(layerId: string, keyframeId: string) {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
const kf = layer.keyframes.find((k) => Math.abs(k.time - time) < KEYFRAME_EPSILON)
|
||||
if (kf) kf.easing = easing
|
||||
const found = findKf(layer, keyframeId)
|
||||
if (!found) return
|
||||
const idx = found.arr.indexOf(found.kf)
|
||||
if (idx >= 0) found.arr.splice(idx, 1)
|
||||
},
|
||||
setKeyframeEasing(layerId: string, keyframeId: string, easing: Easing) {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
const found = findKf(layer, keyframeId)
|
||||
if (found) found.kf.easing = easing
|
||||
},
|
||||
setKeyframeTime(layerId: string, keyframeId: string, newTime: number) {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
const kf = layer.keyframes.find((k) => k.id === keyframeId)
|
||||
if (!kf) return
|
||||
kf.time = Math.max(0, Math.min(this.scene.duration, newTime))
|
||||
layer.keyframes.sort((a, b) => a.time - b.time)
|
||||
const found = findKf(layer, keyframeId)
|
||||
if (!found) return
|
||||
found.kf.time = Math.max(0, Math.min(this.scene.duration, newTime))
|
||||
sortKfs(found.arr)
|
||||
},
|
||||
shiftLayerPivot(
|
||||
layerId: string,
|
||||
@@ -162,21 +258,26 @@ export const useSceneStore = defineStore('scene', {
|
||||
layer.offsetY = (layer.offsetY ?? 0) + dLocalY
|
||||
layer.base.x += dWorldX
|
||||
layer.base.y += dWorldY
|
||||
for (const kf of layer.keyframes) {
|
||||
if (kf.props.x !== undefined) kf.props.x += dWorldX
|
||||
if (kf.props.y !== undefined) kf.props.y += dWorldY
|
||||
const shift = (kfs?: Keyframe[]) => {
|
||||
if (!kfs) return
|
||||
for (const kf of kfs) {
|
||||
if (kf.props.x !== undefined) kf.props.x += dWorldX
|
||||
if (kf.props.y !== undefined) kf.props.y += dWorldY
|
||||
}
|
||||
}
|
||||
shift(layer.keyframes)
|
||||
shift(layer.positionKeyframes)
|
||||
},
|
||||
duplicateKeyframe(layerId: string, keyframeId: string): string | null {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return null
|
||||
const source = layer.keyframes.find((k) => k.id === keyframeId)
|
||||
if (!source) return null
|
||||
const found = findKf(layer, keyframeId)
|
||||
if (!found) return null
|
||||
const { kf: source, arr } = found
|
||||
const fps = this.scene.fps > 0 ? this.scene.fps : 60
|
||||
const offset = Math.max(3 / fps, 0.1)
|
||||
const duration = this.scene.duration
|
||||
const occupied = (t: number) =>
|
||||
layer.keyframes.some((k) => Math.abs(k.time - t) < KEYFRAME_EPSILON)
|
||||
const occupied = (t: number) => arr.some((k) => Math.abs(k.time - t) < KEYFRAME_EPSILON)
|
||||
let target = source.time + offset
|
||||
while (occupied(target) && target < duration) target += offset
|
||||
if (target > duration) target = duration
|
||||
@@ -192,10 +293,76 @@ export const useSceneStore = defineStore('scene', {
|
||||
props: { ...source.props },
|
||||
easing: source.easing,
|
||||
}
|
||||
layer.keyframes.push(newKf)
|
||||
layer.keyframes.sort((a, b) => a.time - b.time)
|
||||
arr.push(newKf)
|
||||
sortKfs(arr)
|
||||
return newKf.id
|
||||
},
|
||||
setLayerSeparated(layerId: string, separated: boolean) {
|
||||
const layer = this.scene.layers.find((l) => l.id === layerId)
|
||||
if (!layer) return
|
||||
if (!!layer.separated === separated) return
|
||||
if (separated) {
|
||||
const posKfs: Keyframe[] = []
|
||||
const rotKfs: Keyframe[] = []
|
||||
const remaining: Keyframe[] = []
|
||||
for (const kf of layer.keyframes) {
|
||||
const posProps = pickProps(kf.props, POSITION_KEYS)
|
||||
const rotProps = pickProps(kf.props, ROTATION_KEYS)
|
||||
const rest = omitProps(kf.props, new Set(['x', 'y', 'rotation']))
|
||||
if (hasKeys(posProps)) {
|
||||
posKfs.push({ id: uid(), time: kf.time, props: posProps, easing: kf.easing })
|
||||
}
|
||||
if (hasKeys(rotProps)) {
|
||||
rotKfs.push({ id: uid(), time: kf.time, props: rotProps, easing: kf.easing })
|
||||
}
|
||||
if (hasKeys(rest)) {
|
||||
remaining.push({ id: uid(), time: kf.time, props: rest, easing: kf.easing })
|
||||
}
|
||||
}
|
||||
sortKfs(posKfs)
|
||||
sortKfs(rotKfs)
|
||||
sortKfs(remaining)
|
||||
layer.positionKeyframes = posKfs
|
||||
layer.rotationKeyframes = rotKfs
|
||||
layer.keyframes = remaining
|
||||
layer.separated = true
|
||||
} else {
|
||||
interface Bucket {
|
||||
props: Partial<AnimatableProps>
|
||||
easing: Easing
|
||||
}
|
||||
const byTime = new Map<number, Bucket>()
|
||||
const addAll = (kfs?: Keyframe[]) => {
|
||||
if (!kfs) return
|
||||
for (const kf of kfs) {
|
||||
const key = Math.round(kf.time * 1e6)
|
||||
let bucket = byTime.get(key)
|
||||
if (!bucket) {
|
||||
bucket = { props: {}, easing: kf.easing }
|
||||
byTime.set(key, bucket)
|
||||
}
|
||||
bucket.props = { ...bucket.props, ...kf.props }
|
||||
}
|
||||
}
|
||||
addAll(layer.keyframes)
|
||||
addAll(layer.positionKeyframes)
|
||||
addAll(layer.rotationKeyframes)
|
||||
const merged: Keyframe[] = []
|
||||
for (const [key, bucket] of byTime.entries()) {
|
||||
merged.push({
|
||||
id: uid(),
|
||||
time: key / 1e6,
|
||||
props: bucket.props,
|
||||
easing: bucket.easing,
|
||||
})
|
||||
}
|
||||
sortKfs(merged)
|
||||
layer.keyframes = merged
|
||||
layer.positionKeyframes = undefined
|
||||
layer.rotationKeyframes = undefined
|
||||
layer.separated = false
|
||||
}
|
||||
},
|
||||
play() {
|
||||
if (this.playing) return
|
||||
this.playing = true
|
||||
|
||||
Reference in new issue
Block a user