From d4c4830fe2234d689d1a34680bddd87d99450f4a Mon Sep 17 00:00:00 2001 From: th
{{ bgVideoError }}
+{{ t('settings.bgVideoHint') }}
diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue
index 145ff08..9176e41 100644
--- a/src/components/Timeline.vue
+++ b/src/components/Timeline.vue
@@ -15,7 +15,14 @@ import {
PhEyeSlash,
PhMagnet,
PhMaskHappy,
+ PhCircle,
+ PhImage,
PhPauseCircle,
+ PhPencilSimpleLine,
+ PhPlus,
+ PhPolygon,
+ PhSquare,
+ PhTextT,
PhTrash,
} from '@phosphor-icons/vue'
import { useSceneStore } from '@/stores/scene'
@@ -27,6 +34,7 @@ import { kfSelectionKey, useKfSelection, type KfSelectionKey } from '@/composabl
import { showLocked } from '@/composables/useShowLocked'
import type {
AnimatableProps,
+ Asset,
Clip,
ClipInstance,
Easing,
@@ -311,6 +319,51 @@ function onScrubPointerUp(e: PointerEvent) {
if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId)
}
+function bracketTimeFromEvent(e: PointerEvent, el: HTMLElement): number {
+ const ruler = el.parentElement as HTMLElement | null
+ if (!ruler) return 0
+ const rect = ruler.getBoundingClientRect()
+ if (rect.width <= 0) return 0
+ const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
+ return snapTime(ratio * duration.value)
+}
+
+function onInBracketPointerDown(e: PointerEvent) {
+ e.stopPropagation()
+ ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
+}
+
+function onInBracketPointerMove(e: PointerEvent) {
+ const el = e.currentTarget as HTMLElement
+ if (!el.hasPointerCapture(e.pointerId)) return
+ e.stopPropagation()
+ const t = bracketTimeFromEvent(e, el)
+ store.setPlaybackRange(Math.min(t, store.playbackOut), null)
+}
+
+function onInBracketPointerUp(e: PointerEvent) {
+ const el = e.currentTarget as HTMLElement
+ if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId)
+}
+
+function onOutBracketPointerDown(e: PointerEvent) {
+ e.stopPropagation()
+ ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
+}
+
+function onOutBracketPointerMove(e: PointerEvent) {
+ const el = e.currentTarget as HTMLElement
+ if (!el.hasPointerCapture(e.pointerId)) return
+ e.stopPropagation()
+ const t = bracketTimeFromEvent(e, el)
+ store.setPlaybackRange(null, Math.max(t, store.playbackIn))
+}
+
+function onOutBracketPointerUp(e: PointerEvent) {
+ const el = e.currentTarget as HTMLElement
+ if (el.hasPointerCapture(e.pointerId)) el.releasePointerCapture(e.pointerId)
+}
+
function onLaneClick(e: MouseEvent) {
if (kfDrag.value) return
const el = e.currentTarget as HTMLElement
@@ -320,6 +373,34 @@ function onLaneClick(e: MouseEvent) {
store.setPlayhead(snapTime(ratio * duration.value))
}
+const canCreateKf = computed(() => {
+ const id = store.selectedLayerId
+ if (!id) return false
+ const layer = store.activeLayers.find((l) => l.id === id)
+ return !!layer && !layer.locked
+})
+
+function addKeyframeForSelected() {
+ const id = store.selectedLayerId
+ if (!id) return
+ const layer = store.activeLayers.find((l) => l.id === id)
+ if (!layer || layer.locked) return
+ const time = snapTime(store.playhead)
+ const sampled = sampleLayer(layer, time)
+ const props: Partial