0.6.0: background video, in/out range, top-level masks, track icons
- background video layer (document-level), syncs to playhead in editor and fullscreen - playback in/out brackets on the scrub ruler; i/o set them, r rewinds to in-point - togglePlay, tick, fullscreen loop, WebM + server FFmpeg export all honor [in, out] - top-level mask layers cascade over everything below until a group boundary - fullscreen preview slaves localTime to the bg video so it stops stuttering - welcome "new project" now actually loads a blank scene (was a no-op) - default canvas background hardened to white via normalizeScene - new keyframe button (+) in the name-spacer, backspace/delete removes selected kfs - drag-end auto-selects the created/shifted keyframe so the path is visible - animation path updates live during drag and spans every selected keyframe - track rows show an icon per asset kind (rect/circle/image/text/polygon/freeform) - default mask mode is negative, buttons read − then + - sticky track-name columns raised above playhead/brackets; orange bracket styling - tests for playback range, blank scene, mask defaults, cascading top-level masks Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
69de48ea8d
commit
d4c4830fe2
17 files changed
+1332
-153
No files matched your search
+30
-3
@@ -46,7 +46,7 @@ import {
|
||||
} from './lib/projectStore'
|
||||
import { importImageFile } from './lib/fileLoader'
|
||||
import { useClipartStore } from './stores/clipart'
|
||||
import { useSceneStore } from './stores/scene'
|
||||
import { createBlankScene, useSceneStore } from './stores/scene'
|
||||
|
||||
const settingsOpen = ref(false)
|
||||
const exportOpen = ref(false)
|
||||
@@ -137,7 +137,7 @@ const clipart = useClipartStore()
|
||||
const history = useHistory()
|
||||
|
||||
function rewind() {
|
||||
store.setPlayhead(0)
|
||||
store.setPlayhead(store.playbackIn)
|
||||
}
|
||||
|
||||
function stop() {
|
||||
@@ -215,6 +215,15 @@ function openFromRecent(project: StoredProject) {
|
||||
loadError.value = null
|
||||
}
|
||||
|
||||
function startNewProject() {
|
||||
const blank = createBlankScene()
|
||||
store.loadScene(blank)
|
||||
history.resetTo(blank)
|
||||
currentProjectId.value = null
|
||||
currentProjectName.value = null
|
||||
loadError.value = null
|
||||
}
|
||||
|
||||
function showSavedToast(msg: string) {
|
||||
savedToast.value = msg
|
||||
setTimeout(() => {
|
||||
@@ -270,6 +279,24 @@ function onKeyDown(e: KeyboardEvent) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!isEditable(e.target) && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey) {
|
||||
const key = e.key.toLowerCase()
|
||||
if (key === 'i') {
|
||||
e.preventDefault()
|
||||
store.setPlaybackRange(Math.min(store.playhead, store.playbackOut), null)
|
||||
return
|
||||
}
|
||||
if (key === 'o') {
|
||||
e.preventDefault()
|
||||
store.setPlaybackRange(null, Math.max(store.playhead, store.playbackIn))
|
||||
return
|
||||
}
|
||||
if (key === 'r') {
|
||||
e.preventDefault()
|
||||
rewind()
|
||||
return
|
||||
}
|
||||
}
|
||||
if (e.code !== 'Space') return
|
||||
if (isEditable(e.target)) return
|
||||
e.preventDefault()
|
||||
@@ -436,7 +463,7 @@ onBeforeUnmount(() => {
|
||||
<WelcomeDialog
|
||||
v-model="welcomeOpen"
|
||||
@open="openFromRecent"
|
||||
@new="() => { currentProjectId = null; currentProjectName = null }"
|
||||
@new="startNewProject"
|
||||
@import="importFromFile"
|
||||
/>
|
||||
<Toast :visible="!!loadError" variant="error">
|
||||
|
||||
Reference in new issue
Block a user