bak
This commit is contained in:
1 parent
cc6b11e61d
commit
b977ee35a8
11 files changed
+1641
-310
No files matched your search
+199
-43
@@ -126,21 +126,86 @@ interface RenderedLayer {
|
||||
props: AnimatableProps
|
||||
}
|
||||
|
||||
const rendered = computed<RenderedLayer[]>(() =>
|
||||
store.scene.layers
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((layer) => {
|
||||
interface RenderedClipLayer extends RenderedLayer {
|
||||
instanceId: string
|
||||
}
|
||||
|
||||
type StackItem =
|
||||
| { kind: 'layer'; item: RenderedLayer }
|
||||
| { kind: 'clip'; item: RenderedClipLayer }
|
||||
|
||||
const stackItems = computed<StackItem[]>(() => {
|
||||
// Clip-edit scope: just render active clip's layers (top of timeline = top of canvas)
|
||||
if (store.activeClipId) {
|
||||
const out: StackItem[] = []
|
||||
const layers = store.activeLayers.slice().reverse()
|
||||
for (const layer of layers) {
|
||||
const asset = assetMap.value.get(layer.assetId)
|
||||
if (!asset) return null
|
||||
return {
|
||||
layer,
|
||||
asset,
|
||||
props: sampleLayer(layer, store.playhead),
|
||||
if (!asset) continue
|
||||
out.push({
|
||||
kind: 'layer',
|
||||
item: { layer, asset, props: sampleLayer(layer, store.playhead) },
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const out: StackItem[] = []
|
||||
const layerById = new Map<string, Layer>()
|
||||
for (const l of store.scene.layers) layerById.set(l.id, l)
|
||||
const clipById = new Map<string, NonNullable<typeof store.scene.clips>[number]>()
|
||||
for (const c of store.scene.clips ?? []) clipById.set(c.id, c)
|
||||
const instancesByTrack = new Map<string, NonNullable<typeof store.scene.clipInstances>>()
|
||||
for (const ci of store.scene.clipInstances ?? []) {
|
||||
const arr = instancesByTrack.get(ci.clipTrackId) ?? []
|
||||
arr.push(ci)
|
||||
instancesByTrack.set(ci.clipTrackId, arr)
|
||||
}
|
||||
|
||||
const order = store.scene.trackOrder ?? store.scene.layers.map((l) => l.id)
|
||||
// iterate bottom → top: reverse the array
|
||||
for (let i = order.length - 1; i >= 0; i--) {
|
||||
const id = order[i]
|
||||
const layer = layerById.get(id)
|
||||
if (layer) {
|
||||
const asset = assetMap.value.get(layer.assetId)
|
||||
if (!asset) continue
|
||||
out.push({
|
||||
kind: 'layer',
|
||||
item: { layer, asset, props: sampleLayer(layer, store.playhead) },
|
||||
})
|
||||
continue
|
||||
}
|
||||
const instances = instancesByTrack.get(id) ?? []
|
||||
for (const inst of instances) {
|
||||
const clip = clipById.get(inst.clipId)
|
||||
if (!clip) continue
|
||||
const trimStart = inst.trimStart ?? 0
|
||||
const trimEnd = inst.trimEnd ?? 0
|
||||
const visible = Math.max(0, clip.duration - trimStart - trimEnd)
|
||||
const offsetIntoBlock = store.playhead - inst.startTime
|
||||
if (offsetIntoBlock < 0 || offsetIntoBlock > visible) continue
|
||||
const localTime = trimStart + offsetIntoBlock
|
||||
// Clip's own layers: reverse too so layer[0] ends up on top
|
||||
for (let k = clip.layers.length - 1; k >= 0; k--) {
|
||||
const cl = clip.layers[k]
|
||||
if (!cl.visible) continue
|
||||
const ca = assetMap.value.get(cl.assetId)
|
||||
if (!ca) continue
|
||||
out.push({
|
||||
kind: 'clip',
|
||||
item: {
|
||||
instanceId: inst.id,
|
||||
layer: cl,
|
||||
asset: ca,
|
||||
props: sampleLayer(cl, localTime),
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
.filter((x): x is RenderedLayer => x !== null),
|
||||
)
|
||||
}
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
function baseNodeConfig(layer: Layer, props: AnimatableProps) {
|
||||
return {
|
||||
@@ -209,6 +274,70 @@ function imageConfig(
|
||||
}
|
||||
}
|
||||
|
||||
function clipBaseConfig(instanceId: string, layer: Layer, props: AnimatableProps) {
|
||||
return {
|
||||
id: `ci:${instanceId}:${layer.id}`,
|
||||
x: props.x,
|
||||
y: props.y,
|
||||
scaleX: props.scaleX,
|
||||
scaleY: props.scaleY,
|
||||
rotation: props.rotation,
|
||||
opacity: props.opacity,
|
||||
visible: layer.visible,
|
||||
draggable: false,
|
||||
listening: false,
|
||||
}
|
||||
}
|
||||
|
||||
function clipRectConfig(item: RenderedClipLayer) {
|
||||
const asset = item.asset as Extract<Asset, { kind: 'rect' }>
|
||||
return {
|
||||
...clipBaseConfig(item.instanceId, item.layer, item.props),
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
offsetX: item.layer.offsetX ?? asset.width / 2,
|
||||
offsetY: item.layer.offsetY ?? asset.height / 2,
|
||||
fill: item.props.fill ?? asset.fill,
|
||||
}
|
||||
}
|
||||
|
||||
function clipCircleConfig(item: RenderedClipLayer) {
|
||||
const asset = item.asset as Extract<Asset, { kind: 'circle' }>
|
||||
return {
|
||||
...clipBaseConfig(item.instanceId, item.layer, item.props),
|
||||
radius: asset.radius,
|
||||
offsetX: item.layer.offsetX ?? 0,
|
||||
offsetY: item.layer.offsetY ?? 0,
|
||||
fill: item.props.fill ?? asset.fill,
|
||||
}
|
||||
}
|
||||
|
||||
function clipTextConfig(item: RenderedClipLayer) {
|
||||
const asset = item.asset as Extract<Asset, { kind: 'text' }>
|
||||
return {
|
||||
...clipBaseConfig(item.instanceId, item.layer, item.props),
|
||||
text: asset.text,
|
||||
fontSize: asset.fontSize,
|
||||
offsetX: item.layer.offsetX ?? 0,
|
||||
offsetY: item.layer.offsetY ?? 0,
|
||||
fill: item.props.fill ?? asset.fill,
|
||||
}
|
||||
}
|
||||
|
||||
function clipImageConfig(item: RenderedClipLayer) {
|
||||
const asset = item.asset as Extract<Asset, { kind: 'image' }>
|
||||
const img = imageCache.value[asset.id]
|
||||
if (!img) return null
|
||||
return {
|
||||
...clipBaseConfig(item.instanceId, item.layer, item.props),
|
||||
image: img,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
offsetX: item.layer.offsetX ?? asset.width / 2,
|
||||
offsetY: item.layer.offsetY ?? asset.height / 2,
|
||||
}
|
||||
}
|
||||
|
||||
interface PivotInfo {
|
||||
layerId: string
|
||||
sampled: AnimatableProps
|
||||
@@ -217,7 +346,7 @@ interface PivotInfo {
|
||||
const pivotInfo = computed<PivotInfo | null>(() => {
|
||||
const id = store.selectedLayerId
|
||||
if (!id) return null
|
||||
const layer = store.scene.layers.find((l) => l.id === id)
|
||||
const layer = store.activeLayers.find((l) => l.id === id)
|
||||
if (!layer) return null
|
||||
const asset = assetMap.value.get(layer.assetId)
|
||||
if (!asset) return null
|
||||
@@ -303,7 +432,7 @@ function attachTransformer() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [store.selectedLayerId, rendered.value.length, store.playhead] as const,
|
||||
() => [store.selectedLayerId, stackItems.value.length, store.playhead] as const,
|
||||
async () => {
|
||||
await nextTick()
|
||||
attachTransformer()
|
||||
@@ -332,14 +461,14 @@ function handleStageMouseDown(e: Konva.KonvaEventObject<MouseEvent>) {
|
||||
store.selectLayer(null)
|
||||
return
|
||||
}
|
||||
const match = store.scene.layers.find((l) => l.id === id)
|
||||
const match = store.activeLayers.find((l) => l.id === id)
|
||||
store.selectLayer(match ? match.id : null)
|
||||
}
|
||||
|
||||
function handleDragEnd(e: Konva.KonvaEventObject<DragEvent>) {
|
||||
const node = e.target
|
||||
const id = node.id()
|
||||
if (!store.scene.layers.some((l) => l.id === id)) return
|
||||
if (!store.activeLayers.some((l) => l.id === id)) return
|
||||
store.upsertKeyframe(id, store.playhead, {
|
||||
x: node.x(),
|
||||
y: node.y(),
|
||||
@@ -349,7 +478,7 @@ function handleDragEnd(e: Konva.KonvaEventObject<DragEvent>) {
|
||||
function handleTransformEnd(e: Konva.KonvaEventObject<Event>) {
|
||||
const node = e.target
|
||||
const id = node.id()
|
||||
if (!store.scene.layers.some((l) => l.id === id)) return
|
||||
if (!store.activeLayers.some((l) => l.id === id)) return
|
||||
store.upsertKeyframe(id, store.playhead, {
|
||||
x: node.x(),
|
||||
y: node.y(),
|
||||
@@ -530,31 +659,58 @@ onBeforeUnmount(() => {
|
||||
<v-group :config="viewConfig">
|
||||
<v-group :config="clipGroupConfig">
|
||||
<v-rect :config="backgroundConfig" />
|
||||
<template v-for="item in rendered" :key="item.layer.id">
|
||||
<v-rect
|
||||
v-if="item.asset.kind === 'rect'"
|
||||
:config="rectConfig(item.layer, item.asset, item.props)"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<v-circle
|
||||
v-else-if="item.asset.kind === 'circle'"
|
||||
:config="circleConfig(item.layer, item.asset, item.props)"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<v-text
|
||||
v-else-if="item.asset.kind === 'text'"
|
||||
:config="textConfig(item.layer, item.asset, item.props)"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<v-image
|
||||
v-else-if="item.asset.kind === 'image' && imageConfig(item.layer, item.asset, item.props)"
|
||||
:config="imageConfig(item.layer, item.asset, item.props)!"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<template
|
||||
v-for="(entry, i) in stackItems"
|
||||
:key="
|
||||
entry.kind === 'layer'
|
||||
? `l:${entry.item.layer.id}`
|
||||
: `ci:${entry.item.instanceId}:${entry.item.layer.id}:${i}`
|
||||
"
|
||||
>
|
||||
<template v-if="entry.kind === 'layer'">
|
||||
<v-rect
|
||||
v-if="entry.item.asset.kind === 'rect'"
|
||||
:config="rectConfig(entry.item.layer, entry.item.asset, entry.item.props)"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<v-circle
|
||||
v-else-if="entry.item.asset.kind === 'circle'"
|
||||
:config="circleConfig(entry.item.layer, entry.item.asset, entry.item.props)"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<v-text
|
||||
v-else-if="entry.item.asset.kind === 'text'"
|
||||
:config="textConfig(entry.item.layer, entry.item.asset, entry.item.props)"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
<v-image
|
||||
v-else-if="entry.item.asset.kind === 'image' && imageConfig(entry.item.layer, entry.item.asset, entry.item.props)"
|
||||
:config="imageConfig(entry.item.layer, entry.item.asset, entry.item.props)!"
|
||||
@dragend="handleDragEnd"
|
||||
@transformend="handleTransformEnd"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-rect
|
||||
v-if="entry.item.asset.kind === 'rect'"
|
||||
:config="clipRectConfig(entry.item)"
|
||||
/>
|
||||
<v-circle
|
||||
v-else-if="entry.item.asset.kind === 'circle'"
|
||||
:config="clipCircleConfig(entry.item)"
|
||||
/>
|
||||
<v-text
|
||||
v-else-if="entry.item.asset.kind === 'text'"
|
||||
:config="clipTextConfig(entry.item)"
|
||||
/>
|
||||
<v-image
|
||||
v-else-if="entry.item.asset.kind === 'image' && clipImageConfig(entry.item)"
|
||||
:config="clipImageConfig(entry.item)!"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</v-group>
|
||||
<v-rect :config="canvasBorderConfig" />
|
||||
|
||||
Reference in new issue
Block a user