This commit is contained in:
th committed 2026-04-26 21:39:11 +02:00
1 parent 367dd68e49
commit b88d174bcb
8 files changed
+2 -353

No files matched your search

+2
View File
@@ -16,3 +16,5 @@ dist-archive
/logo.xcf /logo.xcf
/logo_v2.xcf /logo_v2.xcf
/ruwio.png /ruwio.png
.codex
misc/
-108
View File
@@ -1,108 +0,0 @@
FUTURE FUN: THREE FEATURES TO LAND
==================================
End-of-day backlog of ideas Claude pitched and the user liked. Each
sized for ~one patch bump. Wiggle goes first; the others can wait.
1. WIGGLE — procedural shake modifier (per layer)
-------------------------------------------------
Add a "Wiggle" property to Layer. When on, every sampled frame
adds deterministic noise to position and/or rotation, giving any
layer that classic animator wobble. Pairs thematically with Kaleido:
both are per-layer procedural modifiers stacked on top of the base
keyframe sample.
Data model (src/types/scene.ts):
wiggle?: {
amplitude?: number // pixels for x/y
rotation?: number // degrees for rotation
frequency?: number // Hz (cycles per second)
}
// Defaults when `wiggle` is set but a sub-field is missing:
// amplitude=8px, rotation=2°, frequency=2Hz. Toggle off ⇒ delete.
Math (src/lib/wiggle.ts) — shared truth:
function layerSeed(id: string): [number, number, number]
// 3 stable 32-bit ints derived from the layer id, used as
// independent seeds for x / y / rotation noise streams.
function wiggleNoise(seed: number, t: number): number
// Deterministic [-1, 1] output. Sum of three sin waves with
// different irrational phase offsets — cheap, smooth, no
// dependency on a noise lib.
return (
sin(seed * 12.9898 + t * 78.233) +
sin(seed * 4.31 + t * 11.71 ) +
sin(seed * 7.71 + t * 23.5 )
) / 3
function applyWiggle(props, layer, time): props'
// Adds amp * noise(seedX, t * freq) to props.x, etc.
Wire-in:
- src/lib/interpolate.ts sampleLayer: post-process the result of
the keyframe sample with applyWiggle. ALL renderers go through
sampleLayer (canvas2d, Konva, button-state, clip-internal), so a
single hook here lights up everywhere except the export engine.
- src/lib/exportPlayer.ts: mirror the math in JS inside the engine
source so exports look identical. RUNTIME_VERSION bump.
UI (src/components/PropertiesPanel.vue + i18n):
- New "Wiggle" collapsible section, between Mask and Kaleido feels
natural. Toggle (PhWaveSine icon), then three RangeSliders:
amplitude (0–64 px), rotation (0–45°), frequency (0–10 Hz).
Hidden for button-asset layers (consistent with Kaleido).
Store (src/stores/scene.ts):
- setLayerWiggle(id, patch) with same delete-on-default semantics
as Kaleido (rotation=0 ⇒ delete the field, etc.).
Tests:
- wiggleNoise determinism: noise(seed, t) === noise(seed, t).
- wiggleNoise smoothness: |n(t) - n(t+1/60)| stays bounded.
- sampleLayer with wiggle: x/y/rotation drift around base, return
to base when wiggle is off.
- Engine source carries wiggleNoise; same sample at the same time
matches between editor and engine.
- PropertiesPanel renders the new section + writes wiggle fields
back to the store.
2. MOTION-PATH OVERLAY (selected layer's position keyframes)
------------------------------------------------------------
Visualize a layer's keyframed path on the canvas as a faint dashed
line connecting consecutive position keyframes, with each keyframe
rendered as a draggable dot. The position-keyframe drag-handle
infrastructure (PathFrame, activeSegments in CanvasStage.vue ~2562)
is already 70% there — currently only used when a position-kf is
explicitly being dragged. Promote that overlay to "always on while
the layer is selected".
Scope:
- Toggle in the View menu (or a slash-key shortcut) called "Motion
paths" — global flag persisted in localStorage like
`useShowLocked`.
- For the selected layer (and only the selected layer), draw the
overlay even when nothing is being dragged. Reuse the existing
segment / curve-sampling helpers.
- Color follows layer.color so two layers' paths don't collide
visually.
Bigger lift than Wiggle but mostly leveraging existing code.
3. EYEDROPPER (color sampling from anywhere on the page)
--------------------------------------------------------
Tiny feature using the browser's EyeDropper API. Adds a small
pipette icon next to every color swatch in the PropertiesPanel
(fill, stroke, shadow color). Click → fires `new EyeDropper().open()`
→ user picks any pixel on screen → result sets the swatch.
Caveats:
- Chromium-only API. Hide the button when `window.EyeDropper` is
undefined (Safari + Firefox) — a clean progressive enhancement.
- Permission prompt is browser-handled, no extra plumbing.
Smallest of the three. Good "polish day" snack.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

-68
View File
@@ -1,68 +0,0 @@
# Plan — SVG asset authoring view
## Concept
A new asset kind `svg` (sibling to `button`) with its own sub-editor scope. Created from a "+" button under the Images section. The sub-editor reuses the canvas + shape primitives but has no time axis: each sub-layer is a single static frame.
## 1. Data model — `src/types/scene.ts`
- Extend `AssetKind` union with `'svg'`.
- Add `SvgAsset { id; kind: 'svg'; name; canvas: { width; height; background? }; layers: Layer[] }`.
- Add to the `Asset` discriminated union.
- Layers reuse the existing `Layer` type — single keyframe at `t=0` is all the renderer/sampler needs.
## 2. Store scope — `src/stores/scene.ts`
- New state: `activeSvgAssetId: string | null`.
- Actions (mirroring the button pair):
- `addSvgAsset(name, canvas)` → creates asset, returns it.
- `enterSvgAssetEdit(assetId)` → sets `activeSvgAssetId`, clears layer / clip / button selections, parks playhead at 0.
- `exitSvgAssetEdit()` → nulls scope + selection.
- The existing `addLayer(assetId)` and `addAsset(...)` already write into the active scope (button / clip / main); extend the same scope-resolver so `activeSvgAssetId` is recognised. That means `addSampleRect / addSampleCircle / addSampleText / addSamplePolygon / startFreeformDrawing` all work unchanged inside the SVG scope.
- Block keyframe-creating paths inside SVG scope (`upsertKeyframe`, the "+ keyframe" button, arrow-nudge keyframe writes) — silently bake into the single frame at `t=0` instead of inserting a new one. Cheaper and cleaner than scrubbing them out post-hoc.
## 3. AssetSidebar — `src/components/AssetSidebar.vue`
- Add a `section-action` "+" button to the Images section header (same pattern as Clips/Buttons section).
- Handler: prompt for name (and maybe canvas size — default `300×300`) → `addSvgAsset(...)` → `enterSvgAssetEdit(asset.id)`.
- SVG assets show up in the Images section list as their own row (icon + name + click-to-edit + drag-to-stage to place a layer that references the asset). Pattern matches Buttons section.
## 4. Sub-editor wiring — `src/App.vue`
- Scope footer: add a third branch alongside `activeClipId` / `activeButtonAssetId`. SvgAsset name + back button → `exitSvgAssetEdit()`.
- **Layout swap** (the "left side replaces library" bit): when `activeSvgAssetId` is truthy:
- The `.sidebar` slot renders a new `SvgLayerPanel` instead of `AssetSidebar`.
- The `.timeline` footer area is collapsed / hidden (toggle a `panels-no-timeline` class on `.app` that flips `--timeline-height` to 0 and skips rendering Timeline). Same machinery the TAB-panels feature uses, so the visual transition is consistent.
- Header transport (play/pause/rewind/playhead) stays but should be disabled in SVG scope — there's nothing to play. Easiest: gate them on `!activeSvgAssetId`.
## 5. New component — `src/components/SvgLayerPanel.vue`
A purpose-built layer list. Cannot extract from Timeline — its track-name column is too coupled to the time axis. Surface area:
- One row per layer in `asset.layers`, top = front.
- Per row: type icon, inline-rename name, eye toggle (`editorHidden`), lock toggle, delete.
- Drag-to-reorder calling `moveLayer`.
- Top toolbar with the same shape-creation buttons currently in AssetSidebar (rect / circle / text / polygon / freeform). Reuse the existing `addSample*` handlers — they already obey active scope once step 2 lands.
- Selection state: existing `selectedLayerIds` works as-is.
## 6. CanvasStage — `src/components/CanvasStage.vue`
- Already drives off `activeButtonAssetId` for the button stage. Add a parallel branch for `activeSvgAssetId` that resolves the source layers + canvas size from the SVG asset.
- Skip the playhead animation: sample layers at `t=0` regardless of `store.playhead`. (Or: lock playhead to 0 in SVG scope so existing sampling Just Works.)
- Disable / hide UI not relevant to authoring statics: keyframe overlays, in/out brackets, scrub guidance.
## 7. Rendering the SVG asset back in the main scene — `src/lib/render.ts`
When a layer references an `SvgAsset`, draw its sub-layers via the existing layer/group draw path at `t=0`, scaled into the asset's `canvas` rect (same idea as `drawButtonFrame`). New helper `drawSvgFrame(ctx, asset, scene, images)` — small, reuses `drawMaskedGroup` like the button code does.
## 8. Persistence
No work. `SvgAsset.layers` is just `Layer[]`; goes through `projectForSave` → JSON → `.ruw` round-trip transparently. The only gate is the `AssetKind` union — once that's extended, save/load + zip/unzip + scene-clone paths all continue to work unchanged.
## 9. i18n
Strings: section-action button label/tooltip, scope footer label, layer-panel toolbar tooltips, the new-svg prompt copy. EN + DE.
## 10. Tests (devtools/tests.ts)
- Store: `addSvgAsset` shape, `enterSvgAssetEdit` flips scope + clears selection, `exitSvgAssetEdit` reverses it, `addLayer` inside SVG scope appends to `asset.layers`.
- Sidebar: "+" button under Images creates an asset and enters edit mode.
- App: while `activeSvgAssetId`, sidebar shows the layer panel and the timeline area is hidden.
- Render: a scene containing a layer referencing an SVG asset draws the asset's child shapes (snapshot via canvas pixel sample, like the existing button rendering tests).
- Round-trip: save + reload preserves `kind: 'svg'` and the nested `layers`.
## 11. Decisions (confirmed)
- **Canvas size on creation**: inherit from current scene; user can resize later via the existing resize button.
- **Background**: transparent.
- **Placement after creation**: drop user straight into the sub-editor.
- **Existing keyframe data**: strip on entry — SVG sub-layers carry a single t=0 keyframe.
Implementation order: 1–2 (types + store) → 3 + 4 (sidebar entry + scope swap) → 5 (layer panel) → 6 (canvas stage scope) → 7 (main-scene render) → 8 (i18n) → 9 (tests).
-177
View File
@@ -1,177 +0,0 @@
rotation povot gibts nur bei rect und das objekt rotiert nicht um den pivot sondern immer ums zentrum
assets sidebar braucht kleine preview thumbnails die man an und aus machen kann
bei bedarf muss man eine spur in der timeline auf "separate position and rotation" umstellen können. dazu führen wir ein context-menü auf spuren ein
die animations-engine muss von der eigentlichen animation getrennt sein weil man ja auch mehrere animationen auf einer webseite haben kann und dann will man das nicht alles redundant haben
die aktuell nicht bearbeiteten timelines müssen etwas weniger opacity haben
control-drag muss die bühne verschieben
bei timeline zoom wird bei der aktiven spur der keyframe und connect line vorne nicht weggeschnitten
der playhead soll kein gedrehtes quadrat sein so wie die keyframes sondern ein aufrechtes rect
baue eine frontent testing engine. mit shift+control+alt+t muss such die app dann selbst durchtesten. schreibe tests für die funktionen die wir bisher haben
fehlerreport mit allen relevanten informationen muss automatisch in die zwischenablage kopiert werden. schreib dir da alles rein was du zum fixen brauchst
rechtsklick auf keyframe muss die auswahl von "still frame" bieten. von einem ein still frame aus findet keine tween animation statt. das objekt bleibt einfach stehen. der keyframe wird als dot dargestellt statt als diamant
text-objekte muss man neben rect und kreis zur verfügung haben
ich möchte ein fullscreen preview auf shift-space
bitte fehlende tests ergänzen
neues feature: clips. ein clip ist eine eigene timeline. diese timelines sind bibliotheks-objekte und können in die haupt-timeline gezogen werden. sie landen auf einer oder mehreren clip-spuren. ein clip wird dort nur als block angezeigt, nicht als keyframes. man kann den clip durch doppelklick aber öffnen wie die haupt-timeline. durch DnD kann man die position des clips in der timeline verschieben.
die clips muss man am anfang und ende durch ziehen verkürzen können. verkürzungen müssen in der UI sichtbar sein durch anfasser die man auch wieder anders justieren kann
für user-interaktionen (wie neues text-objekt) brauchen wir eigene popups. keine browser stock confirms
<button data-v-228c0a9d="" class="breadcrumb-btn"> ← Haupt-Timeline </button> verdeckt die timeline switches. das muss etwas weiter oben am bottom des asset asides angezeigt werden
den zurück pfeil bitte als phosphor icon machen
spuren muss man einfärben können. wir bieten die grundfarben als color tiles bei rechtsklick auf die spur an gilt auch für clipspuren
clips draggen darf nur auf der x-achse gehen. y verschiebung darf nicht möglich sein - das ist irreführend
das timeline panel muss man per anfasser höher ziehen können
spuren muss man locken können. sie dürfen dann nur noch die halbe höhe haben und an der zeitlinie muss erkennbar sein das nicht bearbeitet werden kann
per box select muss man über mehrere spuren keyframe multiselection durchführen können um sie zusammen in der zeit zu verschieben
rect circle und text brauchen wir nicht als instanzen in der lib. da tauchen nur importierte elemente auf. diese muss man auch mehrmals einfügen können per DnD oder "+"
ALLE texte der gui bitte in englisch
wir machen mal ein about popup und verwalten ab sofort eine version. wir starten mit 0.1.0
about öffnet bei click auf den app namen oben links
ein element rein draggen muss das element (grafik) als draggable anzeigen, nicht den bibliothekseintrag
context menüs ragen unten immernoch aus dem viewport raus
rechts click menu muss überall unterdrückt werden wo wir noch keins haben. das browser context menu darf nirgends angezeigt werden
maskenlayer sind tracks mit einem pfad-objekt (circle, rect, text) alles unter einem masken track wird davon maskiert. allerdings immer nur in einer gruppe. elemente außerhalb der gruppe mit der maske werden normal dargestellt. es kann mehrere masken tracks geben. ein maskentrack kann entweder positiv oder negativ maskieren. ein maskenlayer mus zwei ansichten haben: normale elemente (damit man sie zieht zum bearbeiten) und masking (damit sie maskieren)
tests tests tests ;)
tasten i und o setzen die brackets. die brackets müssen orange. wenn der playhead auf der bracket ist hat der playhead vorrang beim draggen. heißt wenn die maus über dem playhead ist bewegt man den egal ob da auch eine klammer ist. klammern und playhead müssen weiß werden bei hover (bzw. schwarz in light theme)
bei langer timeline (wenn man hin und her scrollen kann) liegen playhead und needle sowie die klammern über der track-label-spalte. die track labels müssen z-index ganz nach oben - durekt unter sämtlichen popups etc.
kann der animationspfad sich live mit ändern wenn man das objekt bewegt?
wir bauen einen button schritt für schritt:
zuerst nehmen wir das konzept der clips. ein clip kann ja in der timeline frei positioniert werden.
ein button kann das auch, nur das er grundsätzlich jede beliebige länge auf der zeitachse haben kann.
er ist ein loop-objekt. dadurch loopt er seine zeitachse auch durch wenn die haupt zeitliste auf pause steht.
von seinem mouse-hover state hängt ab, wencher zusätzliche track on top sichtbar wird.
der grundloop läuft immer, aber
die states sind verschiedene zeitleisten innerhalb des buttons die umgeschaltet werden
wir machen 2 arten von buttons. damit wir erstmal weiter kommen zuerst das sehr simple konzept des 5 frame buttons.
normal / hover / pressed / disabled / hitarea
die zeitachse sieht bei diesem objekt etwas anders aus als eine normale zeitleiste: es gibt nur diese 5 frames (das kann man dann auch breiter darstellen)
im ersten frame platziert man die grundgrafik und mutiert die in den 4 weiteren
das hitarea ist nur ein rect oder circle auf den die maus reagiert. das bauen wir erstmal so.
layer kann man prinzipiell auch hier so viele haben wie man will, die müssen aber nicht zwingend in allen 4 grafik-frames auch was sichtbares beinhalten.
hast du das verstanden? gibts fragen?
die tracklisten-spalte muss genauso breit wie die assets spalte darüber. .name-spacer in.scrub-bar muss fixed sein und darf sich beim horizintalen scrollen der tracks nicht da wegbewegen
der button der aus der clip-editor ansicht raus führt sieht gut aus
<div data-v-7a7a37b1="" class="scope-footer"><button data-v-7a7a37b1="" class="scope-back"><svg data-v-7a7a37b1="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="12" height="12" fill="currentColor"><g><path d="M228,128a12,12,0,0,1-12,12H69l51.52,51.51a12,12,0,0,1-17,17l-72-72a12,12,0,0,1,0-17l72-72a12,12,0,0,1,17,17L69,116H216A12,12,0,0,1,228,128Z"></path></g></svg> Main timeline</button><span data-v-7a7a37b1="" class="scope-current" title="Clip"><svg data-v-7a7a37b1="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="12" height="12" fill="currentColor"><g><path d="M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM40,88h80v80H40Zm96-16V56h32V72Zm-16,0H88V56h32Zm0,112v16H88V184Zm16,0h32v16H136Zm0-16V88h80v80Zm80-96H184V56h32ZM72,56V72H40V56ZM40,184H72v16H40Zm176,16H184V184h32v16Z"></path></g></svg> Clip</span></div>
der button der aus der button ansicht raus führt soll genauso sein, das ist momentan ein kleiner exit button links neben dem state switch
also ich würde stark bevorzugen wenn du den code ließt statt immer den vite dev zu nehmen
ich kann das visuell kontrollieren und dir screenshots von den sachen machen. und der handle ist genau wie vorher
diese spur darf nur erscheinen wenn man überhaupt schon mindestens einen clip hat.
<div data-v-228c0a9d="" class="track clip-track-dropzone"><span data-v-228c0a9d="" class="dropzone-hint">Drop a clip here to add a new track</span></div>
und der text muss immer zentriert stehen bleiben und darf nicht horizontal mit der timeline mitgescrollt werden
im button editor zeigt das preview mit angewendeten masken die hauptbühne an - nicht die button-bühne
die farbe einer gruppe muss automatisch auf alle kinder angewendet werden. es sei denn man färbt einen track HINTERHER anders ein
im button dürfen keyframes nur auf den 4 states einrasten
der clip-art drawer muss bevor er reingeschoben wird schon so tun als hätte er die volle breite damit die thumbs nicht hin und her purzteln beim verbreitern. die position von den thimbs muss vor dem slide in feststehen
irgendwie ist das verschieben von elementen kaputt gegangen. in dem szenario habe ich 2 einfache elemente auf der bühne. wenn ich das eine verschiebe verschiebt sich das andere und das was ich eigenlich verschoben habe snapt zurück, das betrifft das drüber liegende element. bei dem unteren layer gehts. das war mir aber schon vorher mal aufgefallen in einem anderen film
shadow einstellungen auf keyframes werden in preview und editor nicht gleich behandelt. eine animation des shadows findet im preview nicht statt
die inline hilfe texte in den settings sind zu klein. das kann man nicht lesen
warum ist eigentlich still frame easing disabled? man muss doch zu einem still-frame hin easen können. verstehe den grund nicht
wenn man den leeren teil des content-views zieht (außerhalb der canvas constraints) muss canvas move auch ohne control key gehen
export dateien heißen noch animatr. wir haben in RUWIO umbenannt
die tooltips sind hässlich. bau ein schöneres custom tooltip system ein. nimm dir zeit und such alle tooltips raus die wir haben. wenn es sinnvoll ist eine funktion besser zu erklären kann ein tooltip auch eine inline hilfe sein. das muss man unter settings -> interface abstellen können
buttons müssen einen pointer cursor anzeigen
bei den button timelines zeigen wir '+' icons an auf leeren frames. da wird dann der vorrige hin dupliziert
die track-lanes der nicht aktiven tracks zeigen durchsichtige keyframes. das muss genauso aussehen wie bei den aktiven - nur der ganze track ca. 50% etwas ausgefaded
element auf gruppe ziehen muss auch in der track label spalte gehen
der bereich wo man den timeline-bereich größer ziehen kann ist nur noch 1px. da hatten wir 15px vorher. man trifft diesen anfasser jetzt nicht mehr
kann man bei den button-states 200ms transitions einbauen?
elemente in einer gelockten gruppe kann man bearbeiten. bearbeitungsmöglichkeit muss sofort entzogen werden wenn eine gruppe gesperrt ist
wir brauchen mehrfachselection von tracks (shift-click) und gruppiegen. also rechtsklick -> group
wir brauchen mehrfachselection von tracks und konvertierung in clip. also rechtsklick -> convert to clip
dann werden die tracks direkt als neuer clip an ort und stelle eingefügt
----
ich würde an dieser stelle mal ein paar regression-tests hinzufügen und dann committen