From 31604052ca195e64440c7d0619b8a7336eb125df Mon Sep 17 00:00:00 2001
From: th
Date: Mon, 20 Apr 2026 23:37:49 +0200
Subject: [PATCH] revert macro-clips button feature (keep markers + scripts)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The button authoring ended up too far from the intended model after
three correction passes. Pulling it out entirely so we can rebuild
from a clean spec. Markers, script tracks, script frames, the Action
editor, the Script DSL and the runtime for all of the above stay —
they work.
Removed:
- types: ButtonState, StateTransition, ButtonHitArea, ButtonClip,
ButtonInstance, setButtonState/setButtonDisabled Action variants,
Scene.buttonClips/buttonInstances
- store: activeButtonClip* getters, all add/remove/setButton* actions,
seedDefaultButtonClip, enterButtonEdit/setActiveButtonState/
exitButtonEdit, button-label resolution in actionRuntime
- useButtonRuntime composable, buttonBlend helper,
ButtonInstanceDialog, sidebar Buttons section, Timeline button
rendering + pointer handlers, CanvasStage button rendering +
pointer state machine, exportPlayer button runtime + hit-testing
- DSL commands state/enable/disable and their parse errors
- 35 button-related tests + helpers; stale testing doc; stale design
doc
Re-applied three uncommitted fixes that were unrelated to the button
work:
- clicking a marker no longer jumps the playhead (only drag / dblclick
act on it)
- script tracks get a right-click context menu (rename + delete,
color in the existing palette)
- onTracksPointerDown excludes .script-lane / .script-frame so
clicks on empty lanes or on frame dots reach the frame-open path
instead of being swallowed by box-select pointer capture
Co-Authored-By: Claude Opus 4.7 (1M context)
---
docs/macro-clips-and-scripts.md | 437 -----------------------
docs/macro-clips-testing.md | 244 -------------
src/components/ActionEditor.vue | 70 +---
src/components/AssetSidebar.vue | 86 -----
src/components/ButtonInstanceDialog.vue | 368 -------------------
src/components/CanvasStage.vue | 207 -----------
src/components/Timeline.vue | 227 ++++--------
src/composables/useButtonRuntime.ts | 159 ---------
src/devtools/tests.ts | 452 +-----------------------
src/i18n/messages.ts | 39 --
src/lib/actions.ts | 14 +-
src/lib/buttonBlend.ts | 55 ---
src/lib/exportPlayer.ts | 256 +-------------
src/lib/scriptDsl.ts | 51 +--
src/stores/scene.ts | 178 +---------
src/types/scene.ts | 50 +--
16 files changed, 84 insertions(+), 2809 deletions(-)
delete mode 100644 docs/macro-clips-and-scripts.md
delete mode 100644 docs/macro-clips-testing.md
delete mode 100644 src/components/ButtonInstanceDialog.vue
delete mode 100644 src/composables/useButtonRuntime.ts
delete mode 100644 src/lib/buttonBlend.ts
diff --git a/docs/macro-clips-and-scripts.md b/docs/macro-clips-and-scripts.md
deleted file mode 100644
index c1922bc..0000000
--- a/docs/macro-clips-and-scripts.md
+++ /dev/null
@@ -1,437 +0,0 @@
-# Macro Clips & Scripts — Featureset Plan
-
-Interaktive Buttons plus eine kleine Script-Sprache, mit der Timelines
-gesteuert werden können.
-
-## Getroffene Entscheidungen
-
-1. Buttons sind **Definitionen + Instanzen** (wie normale Clips): einmal
- definieren, beliebig oft auf Clip-Tracks instanziieren.
-2. `pressed` ist **rein visueller State**. Aktionen hängen an einem
- `onClick` pro Button-**Instanz** (nicht pro Definition), damit derselbe
- Button in unterschiedlichen Kontexten andere Dinge tun kann.
-3. Script-Actions feuern **nur bei vorwärts laufendem Playback**, nicht
- beim Scrubben und nicht beim Rückwärtslaufen.
-
-## Datenmodell
-
-### Clip mit Varianten
-
-```ts
-type Clip = AnimationClip | ButtonClip
-
-interface AnimationClip {
- kind: 'animation' // default, bestehendes Verhalten
- id: string
- name: string
- duration: number
- layers: Layer[]
-}
-
-type ButtonState = 'normal' | 'hover' | 'pressed' | 'disabled'
-
-interface ButtonClip {
- kind: 'button'
- id: string
- name: string
- states: Record // 4 Zellen, jeweils ein Stack
- transitions?: StateTransition[]
- hitArea?: { width: number; height: number; offsetX?: number; offsetY?: number }
-}
-
-interface StateTransition {
- from: ButtonState
- to: ButtonState
- duration: number
- easing: Easing
-}
-```
-
-### Instanz-Erweiterung
-
-```ts
-interface ButtonInstance extends ClipInstance {
- // clipId zeigt auf einen ButtonClip; Trim ist irrelevant
- onClick?: Action[]
- disabled?: boolean
- // runtime-only state, nicht persistiert: hover / pressed / disabled flags
-}
-```
-
-### Script-Track
-
-```ts
-interface ScriptTrack {
- id: string
- name?: string
- color?: string | null
- locked?: boolean
-}
-
-interface ScriptFrame {
- id: string
- trackId: string
- time: number
- actions: Action[]
- // optional label zum Anzeigen im Timeline-Badge
- label?: string
-}
-```
-
-### Marker
-
-Marker sind benannte Zeit-Bookmarks auf der Haupt-Timeline, die als
-Sprungziele dienen — sowohl in der DSL (`jump @intro`) als auch über
-die UI (Doppelklick auf Marker springt dort hin).
-
-```ts
-interface Marker {
- id: string
- name: string // muss unique innerhalb der Szene sein (validate!)
- time: number
- color?: string | null
-}
-```
-
-**Scoping:** Marker leben auf Szenen-Ebene. Innerhalb eines
-`AnimationClip`-Edits sind sie nicht sichtbar/nutzbar (Animation-Clips
-haben lokale Zeit). Später evtl. `clip.markers` ergänzen.
-
-**Rendering:** Marker erscheinen auf dem Scrub-Ruler oberhalb der
-Keyframes als kleine Fahnen mit Label. Per Default in der Akzentfarbe,
-optional individuell einfärbbar. Klick setzt Playhead dorthin,
-Rechtsklick öffnet Menü (umbenennen, färben, löschen).
-
-**Namensregeln:** `[a-zA-Z_][a-zA-Z0-9_-]*`, case-sensitive, unique.
-Bei Konflikt zeigt UI einen Fehler, DSL-Parser löst den Namen zur
-Laufzeit auf — fehlender Name = Action wird silent no-op (mit Warning
-im Dev-Overlay).
-
-### Action-Union
-
-```ts
-type Action =
- | { type: 'play' }
- | { type: 'pause' }
- | { type: 'rewindToLoop' } // Playhead → playbackIn
- | { type: 'jumpTo'; time: number } // absolute Zeit
- | { type: 'jumpToFrame'; frame: number } // frame = time * fps
- | { type: 'jumpToMarker'; name: string } // Marker-Name → time
- | { type: 'setButtonState'; instanceId: string; state: ButtonState }
- | { type: 'setButtonDisabled'; instanceId: string; disabled: boolean }
- | { type: 'script'; source: string } // DSL, Phase 7
-```
-
-**Button-Instanz-Referenzen** via `instanceId` (opak) funktionieren im
-Code, aber für den User unhandlich. Jede `ButtonInstance` bekommt
-deshalb zusätzlich ein optionales `label: string` (unique-scoped),
-und Action-Editor-Dropdowns zeigen Labels statt IDs. DSL referenziert
-`btn:label`.
-
-Szene-Erweiterung:
-- `scene.scriptTracks?: ScriptTrack[]`
-- `scene.scriptFrames?: ScriptFrame[]`
-- `scene.markers?: Marker[]`
-- `scene.trackOrder` kann `ScriptTrack`-IDs enthalten.
-
-## Script-Sprache (Phase 7)
-
-Zeilenbasierte Mini-DSL, parsed zu `Action[]`. Comments beginnen mit
-`//`. Argumente space-separated. Tokens:
-
-| Token | Bedeutung |
-| -------------- | --------------------------------------- |
-| `1.5` | Zeit in Sekunden |
-| `#135` | Frame-Nummer |
-| `@intro` | Marker-Name |
-| `btn:label` | Button-Instanz (über ihr Label) |
-| `play`/`pause` | Bare-Command |
-
-MVP-Beispiele:
-
-```
-play
-pause
-rewind // → rewindToLoop
-jump 2.5 // Sekunden
-jump #135 // Frame
-jump @intro // Marker
-state btn:muteToggle pressed
-enable btn:playButton
-disable btn:playButton
-// kommentar, wird ignoriert
-```
-
-Erweiterung später (außerhalb MVP): `if state(btn:mute) == pressed
-then ... else ...`, Variablen, lokale Labels im Script. Fürs erste
-flache Action-Liste.
-
-**Parse-Fehler** werden pro Frame/Button gesammelt und im
-Properties-Panel als roter Hinweis angezeigt; Runtime skippt
-fehlerhafte Frames/Buttons ohne zu crashen.
-
-## Runtime
-
-### evalAction
-
-Zentrale reine Funktion `evalAction(action, runtime)`, shared zwischen
-Editor und Export-Runtime. `runtime`-Interface:
-
-```ts
-interface ActionRuntime {
- fps: number
- playbackIn: number
- playbackOut: number
- duration: number
- markers: Record // name → time, vorauflösend
- setPlayhead(t: number): void
- play(): void
- pause(): void
- setButtonState(instanceId: string, state: ButtonState): void
- setButtonDisabled(instanceId: string, disabled: boolean): void
- warn(msg: string): void // für devtools / console
-}
-```
-
-`evalAction` macht Range-Clamping (Zeit in `[0, duration]`),
-Marker-Lookup, und dispatcht auf `runtime.*`.
-
-### Script-Track-Evaluation
-
-Im `tick`: Frames deren `time` im Interval `(prevPlayhead,
-nextPlayhead]` liegen feuern einmalig, sortiert nach `time`. Kein
-Firing bei:
-- Rückwärts-Scrubbing (`nextPlayhead < prevPlayhead`)
-- Loop-Wrap (wenn Playhead von `playbackOut` zurück auf `playbackIn`
- springt — das Intervall wird als Reset behandelt)
-- Manuellen `setPlayhead` Aufrufen (scrub, keyboard-jump, Marker-Klick)
-
-Das heißt: Firing nur bei natürlichem Vorwärts-`tick`. `lastTickTime`
-speichert den vorherigen Playhead im Store.
-
-### Button-Rendering
-
-- Render den `states[currentState]` Layer-Stack.
-- State-Wechsel → passende Transition suchen (matching `from`/`to`).
- Wenn vorhanden: `interpProgress = 0..1` über `duration * easing`,
- zwischen den beiden State-Stacks per layer-id matchen und blenden.
- Wenn kein Match: Cross-Fade zwischen den gesamten Stacks.
-- Kein Match zwischen `disabled` und irgendwas → instant switch (keine
- Transition für Disable-Toggle).
-
-### Button-Pointer-Handling
-
-- Hit-Area: bbox aller sichtbaren Layer im `normal`-State (cached),
- überschreibbar per `ButtonClip.hitArea`.
-- Event-Logik (State-Machine):
- - `mouseenter` bei state=`normal` → `hover`.
- - `mouseleave` bei state=`hover` oder `pressed` → `normal` (drag-out
- cancelt den Click — Standard-HTML-Button-Verhalten).
- - `mousedown` bei state=`hover` → `pressed`.
- - `mouseup` bei state=`pressed` innerhalb der Hit-Area →
- `onClick` feuern, dann → `hover`.
- - state=`disabled` blockt alle Pointer-Events.
-- `setButtonState`-Action überschreibt die state-machine (für
- programmatische Zustandswechsel). Nächster natürlicher Pointer-Event
- kann wieder übernehmen.
-- `disabled`-Action setzt persistent `state=disabled` bis
- `enable`-Action oder manueller Reset.
-
-## Editor UI
-
-### Sidebar
-- „New Button" → Dialog, erstellt `ButtonClip` mit vier leeren State-Slots
- und einem Default-Rect im `normal`-State.
-- Button-Definitionen erscheinen wie normale Clips in der Clipart-Liste,
- mit kleinem Badge-Icon zur Unterscheidung.
-
-### Button-Edit-Modus
-- Wie `enterClip`, aber mit State-Tabs oben: `N / H / P / D`. Jeder Tab
- editiert den eigenen Layer-Stack. Layer-Namen sollten zwischen States
- matchen, damit Transitions sie paaren können (Info-Tooltip bei
- mismatch).
-- Unter den Tabs: Liste der definierten Transitions mit Add/Remove.
-- Hit-Area-Overlay als Gizmo (draggable rect) wenn im Button-Edit aktiv.
-
-### Properties-Panel — ButtonInstance
-- Label-Feld (unique).
-- `onClick` Action-Editor: Liste mit Add/Up/Down/Remove, pro Action
- eine type-spezifische Param-Form.
-- Disabled-Checkbox (initial state).
-
-### Timeline
-
-**Script-Track-Row:**
-- Kleiner als ClipTrack (kein großer Header), Frames als orange Dots
- mit Action-Count-Badge (`3` bei drei Actions).
-- Klick auf Frame → Action-Dialog.
-- „Add Script Track" im Timeline-Add-Menü.
-
-**ButtonClip-Instanz:**
-- Vier nebeneinanderliegende Zellen (statt einer Leiste), jede ~2×
- so breit wie ein normaler ClipBlock-Frame, beschriftet mit
- `N / H / P / D`.
-- Active-state (zur Laufzeit) wird visuell hervorgehoben.
-- Drag/Drop funktioniert auf ganze Instanz — einzelne Zellen sind
- nicht per Drag verschiebbar.
-
-**Marker:**
-- Scrub-Ruler bekommt einen zweiten Layer über den Ticks für
- Marker-Fahnen (Dreieck/Flag mit Namen rechts daneben).
-- Rechtsklick auf leere Ruler-Fläche → „Add marker here".
-- Shortcut `m` → Marker am Playhead hinzufügen, direkt mit
- Rename-Prompt.
-- Doppelklick auf Marker = rename, Drag = move, Delete/Backspace
- (Marker-selektiert) = remove.
-- Marker-Klick (einfach) springt Playhead dorthin.
-
-### Keyboard-Shortcuts
-- `m` — Marker am Playhead.
-- (bestehend: `i`, `o`, `r`, `space`, `shift-space`.)
-
-## Slicing (Abarbeitungs-Reihenfolge)
-
-### Slice 1 — Datenmodell + Store
-- Types einführen (`Clip` als Union mit `kind: 'animation' | 'button'`,
- `ButtonClip`, `ButtonInstance`, `ScriptTrack`, `ScriptFrame`,
- `Marker`, `Action`).
-- `normalizeScene`: setzt `kind: 'animation'` auf bestehende Clips,
- initialisiert neue Arrays (`scriptTracks`, `scriptFrames`,
- `markers`), validiert Marker-Namen-Uniqueness.
-- Store-Actions:
- - Button: `addButtonClip`, `updateButtonClip`,
- `addButtonInstance`, `setButtonInstanceLabel`,
- `setButtonOnClick`, `setButtonDisabled`, `removeButtonClip`.
- - Script: `addScriptTrack`, `removeScriptTrack`,
- `addScriptFrame`, `removeScriptFrame`, `setScriptFrameActions`,
- `setScriptFrameTime`.
- - Marker: `addMarker`, `removeMarker`, `renameMarker`,
- `setMarkerTime`, `setMarkerColor`.
-- Tests für alle Store-Actions und normalizeScene-Migration.
-
-### Slice 2 — Marker-UI
-- Scrub-Ruler bekommt Marker-Layer mit Flags + Labels.
-- Shortcut `m`, Rechtsklick-Menü, Drag/Doppelklick/Delete.
-- Marker-Panel im Document-Settings (Liste mit Edit).
-- Marker sind reine Navigations-Hilfen — Actions kommen erst später,
- aber das Datenmodell ist bereit.
-
-### Slice 3 — Timeline-Rendering (Buttons + Scripts)
-- Script-Track-Row (analog zu ClipTrack-Row, eigener Renderer).
-- ButtonClip-Instanzen: 4-Zellen-Darstellung.
-- Drag/Drop von ButtonClip-Definition auf Clip-Tracks.
-- Keine Edit-/Runtime-Features — nur Sichtbarkeit.
-
-### Slice 4 — Editor-Dialoge
-- Button-Definition-Dialog + Enter/Exit-Button-Edit-Modus mit
- State-Tabs.
-- Hit-Area-Gizmo im Button-Edit.
-- Properties-Panel für ButtonInstance (Label, onClick-Editor,
- Disabled).
-- Script-Frame-Dialog (Action-Liste + type-specific Forms).
-- Action-Typ-Dropdowns zeigen Marker-Namen + Button-Labels statt IDs.
-
-### Slice 5 — Runtime (Editor)
-- `src/lib/actions.ts` mit `evalAction(action, runtime)`.
-- `useSceneStore.tick`: ScriptFrames feuern nach Forward-Only-Regel.
-- CanvasStage: Pointer-Handler und State-Machine für
- Button-Instanzen, onClick-Dispatch via `evalAction`.
-- Buttons rendern den aktuellen State-Stack (ohne Transitions).
-
-### Slice 6 — Runtime (Export)
-- Port `evalAction` + Button/Script-Handling zu `exportPlayer.ts` und
- `server/render.html` (shared source via Template-Literal aus
- `actions.ts`).
-- RUNTIME_VERSION bump.
-- Export-Tests (headless Eval mit mock runtime).
-
-### Slice 7 — Transitions
-- Transition-Definitions-UI im Button-Edit.
-- Layer-Matching per id/name; Interpolations-Logik in Renderer und
- Export-Runtime.
-- Crossfade als Fallback.
-
-### Slice 8 — Script-DSL
-- Parser (`src/lib/scriptDsl.ts`), klein, selbst-geschrieben, mit
- Error-Reporting.
-- `Action` bekommt `type: 'script'` → zur Compile-Zeit (beim Save) in
- Actions expandiert, Source bleibt für Edit erhalten.
-- Properties-Panel kriegt Code-Editor (Textarea + Parse-Errors).
-- DSL-Tests (Parser + Roundtrip).
-
-## Offene Fragen
-
-1. **Mehrere Script-Tracks parallel?** Vermutlich ja, für Organisation.
- Default: einer wird automatisch angelegt wenn der User die erste
- Script-Aktion anlegt.
-2. **Button-Hit-Area:** automatisch bbox des `normal` States oder muss
- explizit definiert werden? Vorschlag: bbox als Default, manuell
- überschreibbar im Button-Edit.
-3. **Script-Aktionen bei Loop-Wrap:** entschieden → nein, Wrap = Reset,
- kein Firing.
-4. **Keyboard-Shortcuts für Buttons:** nice-to-have, als eigene Action
- `triggerButton` plus Keymap? Später.
-5. **Persistenz der Runtime-States** (welcher Button ist aktuell
- pressed): nein, nicht im Projektfile.
-6. **Button-Toggle-Verhalten:** momentary vs toggle? Vorschlag: erstmal
- nur momentary, Toggle baut der User per Script
- (click → if state==normal then setState pressed else setState normal).
- Später evtl. dediziertes `behavior: 'momentary' | 'toggle'` Feld.
-7. **Marker im Export:** sollen Marker in der exportierten Runtime
- sichtbar sein (z.B. als Overlay im Dev-Mode)? Standardmäßig nein —
- Marker sind Authoring-Tool, für Runtime nur als Sprungziele relevant.
-8. **Label-Uniqueness für Button-Instanzen:** Scene-weit unique oder
- nur pro ClipTrack? Vorschlag: Scene-weit, damit Scripts aus jedem
- Kontext referenzieren können.
-9. **Marker als Script-Frame-Ersatz?** Ein Marker mit Actions wäre
- quasi ein Script-Frame. Vorschlag: nein, separate Konzepte —
- Marker sind passive Sprungziele, Script-Frames sind aktive Aktionen.
- Beide benutzen aber die gleiche Action-Union.
-10. **jumpTo im Animation-Clip-Context:** wenn ein Button in einem
- AnimationClip-Edit sitzt und `jumpTo` ausführt — meint das die
- Haupt-Timeline oder die Clip-Timeline? Vorschlag: immer die
- Haupt-Timeline (dahin scrollt der Playhead sowieso). Clip-interne
- Sprünge wären separate Actions.
-
-## Testing-Strategie
-
-Jede Slice landet mit Tests:
-
-- **Slice 1:** Store-Actions komplett abgedeckt, normalizeScene-
- Migration (alte Clips → `kind:'animation'`), Marker-Uniqueness-
- Validierung.
-- **Slice 2:** Marker-Scrub-Ruler-Interaktion (add/move/delete), `m`
- shortcut.
-- **Slice 3:** Timeline-Rendering per Snapshot/DOM-Assertion.
-- **Slice 4:** Dialog-Flow-Tests (Action hinzufügen/entfernen, Typ
- ändern).
-- **Slice 5:** `evalAction` als reine Funktion mit Mock-Runtime — alle
- Action-Typen abgedeckt; ScriptFrame-Firing-Regeln
- (Forward/Loop-Wrap/Scrub).
-- **Slice 6:** Export-Runtime testet die gleichen Fälle wie Slice 5,
- aber über generierten JS-Code (Fixture-Based).
-- **Slice 7:** Transition-Interpolation an konkreten Beispielen
- (layer-match vs crossfade).
-- **Slice 8:** DSL-Parser-Tests (gültige/ungültige Zeilen, Roundtrip
- `source → actions → source`).
-
-## Abhängigkeiten & Risiken
-
-- `exportPlayer.ts` und `server/render.html` werden deutlich komplexer,
- weil sie Pointer-Handling und einen Action-Interpreter kriegen. →
- Shared module in `src/lib/actions.ts` das beide Varianten konsumieren
- können (Template für Export-Runtime).
-- Der DSL-Parser muss klein bleiben, sonst frisst das Budget.
-- Transitions zwischen States brauchen ein Layer-Matching (id/name) —
- wenn der User States mit unterschiedlichen Layer-Sets baut, gibt's
- nichts sauberes zum interpolieren → Fallback crossfade.
-- Scene-Schema wächst merklich (Clips als Union, neue Arrays). Alte
- Projektdateien müssen via `normalizeScene` migrieren — wir bumpen
- `Scene.version` nicht, solange wir defaults korrekt befüllen. Wenn
- Breaking-Changes nötig werden, dann Version-Bump + explizite
- Migration.
-- ButtonClip-States sind im MVP **statische Posen** (Layer-Stack zu
- t=0). Intern-animierte States (z.B. pulsierender Hover) sind
- bewusst raus — könnten später als „State ist selbst ein Mini-Clip
- mit eigener Duration" ergänzt werden, braucht dann aber
- Zustands-Scheduling.
diff --git a/docs/macro-clips-testing.md b/docs/macro-clips-testing.md
deleted file mode 100644
index 4ad9274..0000000
--- a/docs/macro-clips-testing.md
+++ /dev/null
@@ -1,244 +0,0 @@
-# Macro Clips & Scripts — Testing Guide
-
-Eine Walkthrough für die Features aus `docs/macro-clips-and-scripts.md`.
-Start: App mit `npm run dev`, leeres Projekt öffnen („New Project" im
-Welcome-Dialog).
-
----
-
-## 1. Marker
-
-**Ziel:** Sprungziele auf der Haupt-Timeline setzen und benennen.
-
-1. Playhead an eine beliebige Stelle ziehen (z. B. t = 2 s).
-2. `m` drücken → ein Marker wird an der Playhead-Position angelegt und ein
- Rename-Prompt öffnet sich. Namen eingeben (z. B. `intro`), `Enter`.
-3. Marker erscheint als kleine Fahne oben auf der Scrub-Ruler.
-4. **Interaktionen am Marker:**
- - Einfacher Klick → Playhead springt dort hin.
- - Drag → Marker zeitlich verschieben.
- - Doppelklick → Rename.
- - Marker anwählen, `Backspace`/`Delete` → löscht ihn. `Esc` → entauswählt.
-
-**Erwartung:**
-- Mehrere Marker bleiben zeitlich sortiert.
-- Zweiter Marker mit gleichem Namen bekommt automatisch Suffix (`intro 2`).
-- Namen müssen Scene-weit unique sein (Rename zeigt Fehler-Dialog bei
- Kollision und öffnet den Prompt erneut).
-
----
-
-## 2. Script-Tracks & Action-Frames
-
-**Ziel:** Zeit-gebundene Aktionen definieren, die bei Playback feuern.
-
-1. Im Timeline-Header die **Code-Taste** (`>`-Icon) klicken → ein neuer
- Script-Track wird angelegt (kleinere Zeile als Clip-Tracks).
-2. Auf der leeren Script-Lane an beliebiger Zeitposition klicken → ein
- neuer Action-Frame wird erstellt und der **ScriptFrameDialog** öffnet
- sich.
-3. Im Dialog **Add action** drücken → ein Dropdown zeigt alle Action-Typen.
- - Beispiel: `Pause` auswählen → nur Typ, keine Params nötig.
- - Weitere Action hinzufügen: `Jump to marker` → Marker-Name aus dem
- Dropdown wählen (dafür muss aus Schritt 1 ein Marker existieren).
-4. Dialog schließen mit **Done**.
-5. Mehrere Actions pro Frame sind erlaubt — die Zahl rechts neben dem
- Frame-Dot zeigt deren Anzahl.
-
-**Testen:**
-- Playhead VOR den Action-Frame ziehen, dann `Space` (Play).
-- Playback läuft, und **genau wenn der Playhead über den Frame hinausgeht**,
- feuern die Actions in Listen-Reihenfolge.
-- Beispiel `pause + jumpToMarker intro` → Playback pausiert und
- Playhead springt zum `intro`-Marker.
-
-**Erwartung:**
-- Beim Scrubben (Ziehen des Playheads oder Marker-Klick) feuern
- **keine** Actions.
-- Beim Loop-Wrap (von `playbackOut` zurück nach `playbackIn`) feuern
- ebenfalls keine Actions.
-- Rückwärtsbewegen der Zeit feuert nichts.
-
----
-
-## 3. Button-Clips erzeugen und platzieren
-
-**Ziel:** Einen klickbaren Button definieren, auf die Stage bringen und
-onClick-Actions hinterlegen.
-
-1. In der Asset-Sidebar den Abschnitt **Buttons** → `+` klicken. Namen
- eingeben (z. B. `PlayButton`), `Enter`.
-2. Der Button erscheint in der Liste mit einem Cursor-Icon. Die 4 States
- sind automatisch als bunte Rects vorausgefüllt
- (blau / hellblau / dunkelblau / grau).
-3. Button-Clip aus der Sidebar auf eine Clip-Track ziehen (Drag-and-Drop).
- - Wenn keine Clip-Track existiert, in den Timeline-Bereich unter den
- Tracks droppen → neue Clip-Track entsteht automatisch.
-4. Auf der Clip-Track erscheint ein 4-Zellen-Block (`N H P D`) an der
- Drop-Position.
-5. Auf dem Canvas erscheint der Button (standardmäßig in der Stage-Mitte)
- — wenn der Playhead ≥ Instanz-Startzeit.
-
-**Button editieren:**
-1. Auf den 4-Zellen-Block klicken → **ButtonInstanceDialog** öffnet sich.
-2. **Label** vergeben (z. B. `play`) — wichtig, weil:
- - Im DSL über `btn:play` referenzierbar.
- - Im Dropdown von `Set button state`-Actions sichtbar.
-3. **On click** → Actions definieren (z. B. `Play` und `Jump to marker intro`).
-4. **Start disabled** aktivieren → Button startet grau/schraffiert,
- reagiert nicht auf Klicks.
-5. **Done**.
-
----
-
-## 4. Button-Runtime auf der Stage testen
-
-**Voraussetzung:** Ein Button ist platziert und der Playhead ist auf
-oder nach dessen Startzeit.
-
-1. Maus über den Button bewegen → Farbe wechselt zu *hellblau* (`hover`).
-2. Maus-Down → Farbe wird *dunkelblau* (`pressed`).
-3. Maus-Up (immer noch im Button) → `onClick`-Actions feuern; State geht
- zurück zu `hover`.
-4. Maus-Down und **rausziehen** vor dem Maus-Up → kein `onClick`,
- State kehrt zu `normal` zurück (Standard-Browser-Button-Verhalten).
-5. Disabled-Button hat keine Hover-/Press-Reaktion, ignoriert Klicks.
-
-**Skript-getriggert:**
-- In einem ScriptFrame eine `Set button state`-Action mit Target = Button-Label
- und state = `pressed` anlegen.
-- Beim Playback überqueren → der Button schaltet visuell in `pressed`,
- ohne dass der User hovert.
-- `Set button disabled` / `Enable` toggelt den Disabled-Flag zur Laufzeit.
-
----
-
-## 5. Transitions zwischen States
-
-**Ziel:** Glatter visueller Übergang zwischen States statt hartem Wechsel.
-
-1. ButtonInstance-Dialog öffnen (4-Zellen-Block klicken).
-2. Unter **Transitions** → `+ Add transition`.
-3. Zeile: `normal → hover`, Duration `0.15`, Easing `easeOut`.
-4. Weitere Zeile: `hover → normal`, Duration `0.2`, Easing `easeOut`.
-5. Done.
-6. Auf dem Canvas Maus über den Button → die Farbe **fade**t zwischen
- Blau und Hellblau über 150 ms, statt instant zu wechseln.
-
-**Hinweise:**
-- Transitions sind Clip-scoped, d. h. sie gelten für **alle** Instanzen
- des Buttons.
-- Layer werden zwischen den beiden States per `name` gematcht (beim
- Default-Seed heißen alle Face-Layer schlicht `Face` → werden immer
- interpoliert). Unmatched Layer faden nur rein/raus (Crossfade).
-- Ohne Transition: State-Wechsel ist sofort (Verhalten wie vor Slice 7).
-
----
-
-## 6. Script-DSL testen
-
-**Ziel:** Komplexere Logik in einer kompakten Textform statt manuellem
-Zusammenklicken mehrerer Actions.
-
-1. ScriptFrameDialog öffnen (oder ButtonInstanceDialog).
-2. **Add action** → Typ `Run script` wählen.
-3. Im Textarea:
- ```
- // setzt Pause und springt an den intro-Marker
- pause
- jump @intro
- state btn:play pressed
- disable btn:play
- ```
-4. Done.
-
-**Testen:**
-- Frame überqueren → alle parsed Aktionen feuern in Reihenfolge.
-- Falsche Syntax → Warnung erscheint in der Devtools-Konsole
- (`[animatr] Script line N: ...`). Die fehlerhafte Zeile wird
- übersprungen; valide Zeilen feuern trotzdem.
-
-**DSL-Referenz (aktueller Sprachumfang):**
-```
-// Kommentar (bis Zeilenende)
-
-play // Playback starten
-pause // Playback pausieren
-rewind // Playhead → playbackIn (in-point)
-
-jump 2.5 // Zeit in Sekunden
-jump #135 // Frame-Nummer (durch fps geteilt)
-jump @markerName // benannter Marker
-
-state btn:label // ∈ normal|hover|pressed|disabled
-enable btn:label
-disable btn:label
-```
-
----
-
-## 7. Export-Runtime prüfen
-
-**Ziel:** Sicherstellen dass Buttons, Scripts, Marker im exportierten
-Projekt genauso funktionieren wie im Editor.
-
-1. Menü → Export (oder beliebiger Export-Trigger je nach UI).
-2. `AnimatrPlayer`-HTML-Export wählen → Datei öffnen im Browser.
-3. **Erwartet:**
- - Buttons rendern an ihren Positionen, mit Pointer-Reaktion.
- - onClick feuert die hinterlegten Aktionen (z. B. `jump @intro` springt
- im exportierten Player-Playhead zum Marker).
- - Transitions animieren sauber.
- - Script-Frames feuern bei natürlichem Playback.
-4. Devtools-Konsole im Export-Tab → `window.AnimatrPlayer.__v` sollte
- `'6'` ergeben. Cache-Probleme? Hard-Reload mit Shift+Cmd+R / Ctrl+F5.
-
----
-
-## 8. Runtime-Tests (in-app)
-
-`Ctrl+Shift+Alt+T` öffnet den Test-Runner. Relevante Suiten:
-- `scene store — markers` / `script tracks & frames` / `button clips & instances`
-- `actions — evalAction with mock runtime`
-- `actions — script type expands via evalAction`
-- `scriptDsl — parser`
-- `button runtime — state machine + action wiring`
-- `scene store — tick fires script frames forward-only`
-
-Alle sollten grün laufen.
-
----
-
-## Bekannte Einschränkungen
-
-- **Kein Button-State-Editor in der UI:** Die 4 States sind beim Seed mit
- farbigen Rects vorausgefüllt. Um die Optik anzupassen müsste man
- aktuell `scene.buttonClips[i].states[…]` direkt im gespeicherten
- Projekt-JSON editieren. Ein eigener Edit-Modus ist geplant, aber
- nicht implementiert.
-- **Server-Render (PNG-Sequence / MP4)** bakt Script-Actions und
- Button-Interaktionen bewusst nicht ein — das wäre keine deterministische
- Animation mehr. Scripts wirken nur in der interaktiven
- Browser-Laufzeit (Editor + `AnimatrPlayer`-HTML-Export).
-
----
-
-## Devtools-Cheat-Sheet
-
-Für Tests ohne UI:
-```js
-// Pinia store im Devtools:
-const s = __pinia.state.value.scene
-const store = useSceneStore() // im Vue-Devtools verfügbar
-
-// Button-Instanz direkt einen bestimmten State geben:
-const rt = useButtonRuntime()
-rt.setState(instanceId, 'pressed')
-rt.reset()
-
-// Alle Marker sehen:
-console.table(store.scene.markers)
-
-// Alle Button-Instanzen:
-console.table(store.scene.buttonInstances)
-```
diff --git a/src/components/ActionEditor.vue b/src/components/ActionEditor.vue
index 1c14a1e..8c71b4e 100644
--- a/src/components/ActionEditor.vue
+++ b/src/components/ActionEditor.vue
@@ -8,7 +8,7 @@ import {
} from '@phosphor-icons/vue'
import { useSceneStore } from '@/stores/scene'
import { t } from '@/i18n'
-import type { Action, ButtonState } from '@/types/scene'
+import type { Action } from '@/types/scene'
const props = defineProps<{ actions: Action[] }>()
const emit = defineEmits<{ 'update:actions': [value: Action[]] }>()
@@ -19,19 +19,6 @@ const markerNames = computed(() =>
(store.scene.markers ?? []).map((m) => m.name),
)
-interface ButtonInstanceOption {
- id: string
- label: string
-}
-
-const buttonInstanceOptions = computed(() =>
- (store.scene.buttonInstances ?? [])
- .filter((bi) => !!bi.label)
- .map((bi) => ({ id: bi.id, label: bi.label! })),
-)
-
-const BUTTON_STATES: ButtonState[] = ['normal', 'hover', 'pressed', 'disabled']
-
const ACTION_TYPES: Action['type'][] = [
'play',
'pause',
@@ -39,8 +26,6 @@ const ACTION_TYPES: Action['type'][] = [
'jumpTo',
'jumpToFrame',
'jumpToMarker',
- 'setButtonState',
- 'setButtonDisabled',
'script',
]
@@ -60,18 +45,6 @@ function makeDefault(type: Action['type']): Action {
return { type: 'jumpToFrame', frame: 0 }
case 'jumpToMarker':
return { type: 'jumpToMarker', name: markerNames.value[0] ?? '' }
- case 'setButtonState':
- return {
- type: 'setButtonState',
- instanceId: buttonInstanceOptions.value[0]?.id ?? '',
- state: 'normal',
- }
- case 'setButtonDisabled':
- return {
- type: 'setButtonDisabled',
- instanceId: buttonInstanceOptions.value[0]?.id ?? '',
- disabled: true,
- }
case 'script':
return { type: 'script', source: '' }
}
@@ -179,47 +152,6 @@ function onTypeChange(index: number, newType: Action['type']) {
-