Commit Graph

614 Commits

Author SHA1 Message Date
bflatastic 782a67f228 Fix #63: scaling a layer by its handles follows the pointer
The handle drag rescaled incrementally from the current scale every frame: errors built up,
edge handles scaled both axes, and once a factor reached ~0 the drag could never bring it
back. It also tested the handle at the point where egui recognised the drag (past the drag
threshold), so a quick grab often missed the handle and moved the layer instead.

Now, as in After Effects, the drag scales about the anchor point relative to the layer as the
drag began, so the grabbed point stays under the pointer: corners scale both axes, edges one,
Shift keeps the proportions, and dragging past the anchor flips. Selection-tool gestures start
at the press point.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:43:50 +07:00
BFlat 4373c24962
Merge pull request #73 from storytold/fix/68-open-links
Fix #68: Discord buttons and other links open the browser again
2026-10-05 23:27:45 +07:00
bflatastic 0286a49f0a Fix #68: open links in the system browser (Discord buttons, Help menu, Settings)
The desktop app built eframe without its `links` feature, so egui's open_url was a no-op
on native: the header and Home Discord buttons, Help menu links and Settings links did nothing.
Enabling it links the `webbrowser` crate (MIT OR Apache-2.0), which opens URLs with the
system browser on macOS, Windows and Linux.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:26:29 +07:00
Brandon Thomas e5c58600fd
Merge pull request #60 from storytold/pr/freebsd
FreeBSD: system info reports memory, CPU model and OS version
2026-10-05 11:33:17 -04:00
Brandon Thomas b70c808d60 FreeBSD: system info reports memory, CPU model and OS version
sysctl hw.physmem / hw.pagesize / vm free+inactive page counts, hw.model,
and freebsd-version, with a unit test for the sysctl parser. The FreeBSD
CI workflow itself landed with #69.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:33:06 -04:00
Brandon Thomas 7f3caeea70
Merge pull request #69 from storytold/pr/gpu-gl-fallback
GPU on wgpu's OpenGL backend (FreeBSD / Linux without Vulkan)
2026-10-05 11:29:02 -04:00
Brandon Thomas 3767332efd GPU compositor declines devices whose limits its pipelines exceed
egui-wgpu creates its device on GL with WebGL2's limits (no storage buffers), so the desktop
app on FreeBSD built the compositor's pipelines on it: validation errors (only logged in
release builds) and wrong frames. GpuContext::new now checks the limits its pipelines bind
(storage buffers and textures per stage, bind groups, workgroup sizes) and returns Err, which
keeps the app on the CPU. ui_gpu skips its GPU-vs-CPU comparison when that happens on GL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:52:04 -04:00
Brandon Thomas 6eedf8e2c5 GPU on wgpu's OpenGL backend: renderable depth target, portable shaders, serialised tests
FreeBSD's CI VM (Mesa llvmpipe over GL) failed 22 effectcraft-gpu tests:

- Advanced 3D's camera-depth target was R32Float, which GLES can't render to without
  EXT_color_buffer_float: create_render_pipeline failed validation (release builds only log
  that and draw nothing). The target is now R32Uint holding the f32 bits (renderable on every
  backend, lossless), and GpuContext checks the adapter's format features (render attachment,
  blendable colour) and independent blending up front; when they're missing the render
  pipelines are never built and Advanced 3D renders on the CPU.
- Signed `%` in the shaders: naga emits GLSL `%`, undefined for negative operands, which broke
  the VR effects' horizontal wrap and Cell Pattern's tiling. A shared `imod` never takes `%` of
  a negative number.
- asin/acos: Mesa's are coarse approximations (~1e-4 rad), visible in every lens/sphere/cylinder
  mapping. `asin_p`/`acos_p` (Cephes' asinf polynomial) are f32-accurate on every backend.
- "Could not lock adapter context": the GL backend has one context lock per adapter with a
  timeout, so tests driving devices concurrently panic. Every gpu test helper that hands out a
  device holds one process-wide lock for the calling test; ui_web_job_gpu's two device tests
  share a lock too.
- On GL, up to 1 % of pixels may flip across a decision boundary (division through a
  reciprocal, unfused fma); the GPU-vs-CPU comparisons allow that there only.

Also adds the FreeBSD workflow (from the FreeBSD CI PR) so this branch is checked there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:08:21 -04:00
BFlat 24fcbdf3d2
Merge pull request #62 from storytold/pr/release-0.3.0
Release: EffectCraft v0.3.0
2026-10-05 20:42:35 +07:00
bflatastic 044fc32a9c Release: EffectCraft v0.3.0
Bump the workspace version 0.2.0 -> 0.3.0 (cargo xtask version set 0.3.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:42:11 +07:00
BFlat b9ffd889bc
Merge pull request #61 from storytold/m13-face-ml
M13.36: face tracking trained model (MediaPipe Face Landmarker, pure Rust)
2026-10-05 20:40:31 +07:00
bflatastic e43bf66775 M13.36: face tracking trained model — MediaPipe Face Landmarker on a pure-Rust TFLite interpreter
- effectcraft-segment: a FaceModel interface (find a face in a region, follow it; a Topology
  maps the model's points to the tracker's landmarks and outline), a TensorFlow Lite reader and
  interpreter (float ops, float16 weights folded; matches ai-edge-litert to ~1e-6), and
  MediaPipe Face Landmarker (Google, Apache-2.0: BlazeFace + Face Mesh V2, 3.8 MB, pinned
  SHA-256). The registry gains a task and authors per model; installed weights get a NOTICE.
- effectcraft-track: FaceTracker::new_with uses the model, falls back to the classical fitter
  when it finds no face; chin and jaw come from the outline either way so measurements agree.
- Engine: roto_models becomes models (both tasks); face.models / face.model.* commands,
  Settings ▸ Face Tracking, `wait` on select/install/download for scripts; track.mask reports
  faceModel.
- Credits: ATTRIBUTION.md lists the optional models and the Apache-2.0 specs the code follows.
- Docs: README, ROADMAP (G7), gaps, parity (TRK-3 score unchanged pending G1), agents,
  preferences, architecture, segment README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:39:45 +07:00
Brandon Thomas e2f027f374
docs: point agents to the shared test corpora (#59)
Real-file test oracles live in storytold/photocraft-corpus, explained in
craftrules standards/test-corpora.md. Never commit large binary fixtures
here; fetch them pinned and sha256-verified.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:24:13 -04:00
BFlat 4624c00308
Merge pull request #58 from storytold/m13-roto-ml
M13.35: Roto Brush trained model — swappable segment module, MobileSAM in pure Rust
2026-10-05 17:57:36 +07:00
bflatastic 174ab2b6af Merge main (gap assessment) into M13.35: keep parity numbers, record G7's decision and evidence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:57:09 +07:00
bflatastic 7579de21c6 M13.35: Roto Brush trained model — swappable segment module, MobileSAM in pure Rust
- New L1 crate effectcraft-segment: the MaskModel interface (prompts → foreground probability),
  a registry of open-licensed models (licence must be MIT/Apache/BSD/…; URL, size, SHA-256),
  a PyTorch checkpoint reader (zip + pickle subset), dependency-free SHA-256, and MobileSAM
  (TinyViT-5M + SAM prompt encoder / two-way transformer / mask decoder) on rayon and ndarray's
  safe GEMM. Loads the official mobile_sam.pt as published; checked against candle
  (embedding 0.3% RMS, 99.9% equal mask pixels); ~0.7 s/frame encoder at 32 threads, cached
  per frame, ~0.1 s decoder.
- track::roto: segment_with / propagate_with prompt a model with the strokes or the flow-warped
  matte (box, core points, mask); its probability is a strong, unblurred prior for the same
  graph cut (strokes stay hard constraints); a failing model or one disagreeing with the flow
  (IoU < 0.5) is ignored. Prior gets a weight.
- effects::roto: Version 2.0 / 3.0 use the model chosen in Settings (set_model / model_for);
  the model id is part of the chain seed.
- engine: roto_models (roto.models, roto.model.select / install / download / remove), weights in
  the models folder (host::config_dir()/models, shared by the app, CLI and MCP), background
  loading, downloads through the system curl, verified; Settings ▸ Roto Brush page (prefs
  roto.model). host::config_dir moved from the desktop app.
- Tests: segment (kernels vs direct sums, checkpoint reader, SHA-256 vectors, registry), track
  (fake model: prior shapes the cut, failing / disagreeing models ignored; real weights with
  EFFECTCRAFT_MOBILESAM: base IoU 0.989, propagated ≥ 0.980 vs classic 0.973), engine (registry,
  verification, selection; real install + load), MCP.
- Docs: README, ROADMAP, parity (MSK-4 done: 90/2/0), preferences, agents, architecture,
  crates/segment/README.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:55:02 +07:00
Brandon Thomas aa4656b543 Docs: honest gap assessment (docs/gaps.md), roadmap workstreams G1–G9, README status
parity.md's ≈99% measures breadth only. docs/gaps.md assesses real-world
readiness by dimension (fidelity, .aep interop, stability, platform
reliability, performance, media, AI tools, maturity) with evidence, and
sets prioritised workstreams with done criteria. ROADMAP, README, parity.md,
AGENTS.md and CLAUDE.md point agents at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:32:19 -04:00
BFlat 11d4885764
Merge pull request #57 from storytold/m13-audio-scrub
M13.34: audio scrubbing (Ctrl/Cmd-drag the current time)
2026-10-05 15:19:12 +07:00
bflatastic 5afc25d0d8 M13.34: audio scrubbing (Ctrl/Cmd-drag the current time)
- audio::AudioScrub: an open output that plays a faded snippet (one frame, 30–100 ms) of the
  comp's mix at each new frame, replacing what is still queued; holding still repeats nothing.
  The mixing is shared with the preview feeder (mix_block); AudioFeed::clear.
- The Timeline ruler: Ctrl/Cmd-drag scrubs audio (EffectcraftApp::scrub_audio opens the output
  through the audio device hook on first use). The Audio panel meters follow the snippets; an
  idle second closes the output.
- playback.scrubAudio {time?} for agents.
- Tests: ui_audio_scrub (snippet length, fades, same-frame no-op, replace not pile up, silent
  comps, Cmd-drag in the ruler, idle close) with a fake device. Docs: parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:18:54 +07:00
BFlat 9607be471b
Merge pull request #56 from storytold/m13-nested-markers
M13.33: nested comp markers on the precomp layer bar
2026-10-05 15:12:22 +07:00
bflatastic f013bdd8d7 M13.33: nested comp markers on the precomp layer bar
- commands::markers::nested_markers: a precomp layer's comp markers at their times in the
  containing comp (start and stretch in closed form; with time remapping or a stretch over
  protected regions, the first frame whose source time reaches them), within In–Out.
- markers.nested {layer}: the same list for agents.
- Timeline: drawn on the precomp bar as outlined read-only markers (positions cached per
  revision), hover names them, double-click opens the nested comp at the marker; automation id
  timeline.layer.<id>.nestedMarker.<i>.
- Tests: engine (start, stretch, trim, time remap, non-precomp error), UI (draw, label, open).
  Docs: parity, control protocol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:12:06 +07:00
BFlat 1ac55c33a3
Merge pull request #55 from storytold/m13-new-viewer
M13.32: View ▸ New Viewer — several Composition viewers
2026-10-05 15:00:30 +07:00
bflatastic 97390f15fb M13.32: View ▸ New Viewer — several Composition viewers
- PanelKind::Viewer(n): more Composition viewers (panels::viewers). One is active (the full
  interactive viewer, showing the active comp); the others show their own comp's frame at that
  comp's current time (CPU or GPU frames, their own textures) and become active on a click or
  their tab, their comp becoming the active comp.
- view.newViewer (View menu, the Composition panel menu, Alt+Shift+N): a new viewer of the
  active comp next to the one in use, which is locked. Opening a comp while the active viewer
  is locked shows it in an unlocked viewer, or a new one when every viewer is locked. Closing
  the active viewer hands over to another with its comp. Tabs are named after their comp, with
  their own lock and swatch; Preview full screen maximizes the active viewer.
- UI state: viewers (comp per viewer) and active_viewer (serde).
- Tests: ui_viewers (lock on New Viewer, routing, click to activate, all locked → new viewer,
  close hand-over, tab titles; an ignored snapshot). The bounded-drag frames test asserts what
  holds when freed addresses are reused. Docs: parity, control protocol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:00:11 +07:00
BFlat 6a5abad753
Merge pull request #54 from storytold/m4-content-keyed-frames
M4.14: content-keyed RAM preview frames (M4 done); proxies in the disk cache key
2026-10-05 14:39:33 +07:00
bflatastic 26c714db7f M4.14: content-keyed RAM preview frames; proxies in the disk cache's content key
- FrameKey's identity is the comp's content (frames::comp_content), not the project revision:
  an edit keeps the cached frames of comps it doesn't touch and undo finds the frames of the
  state it returns to. The identity hashes the addresses of the comps a comp draws (~0.1 ms on
  the 1500-layer test project; the full content hash took ~117 ms), by value the footage, solids,
  proxies and project settings; with expressions, every item. Frames::content_of keeps a
  project snapshot per identity with frames (or in flight), so Arc::make_mut always copies an
  edited comp and its address can't be reused meanwhile; past 64 identities the least recently
  used go with their frames (long drags).
- drop_stale is gone (frames of other contents are evicted least-recently-shown first).
- disk_cache::comp_content_key covers items' proxies and Use Proxy (it served stale frames).
- Tests: frames (content keys, edits elsewhere and undo, bounded drags, in-place edit safety),
  ui_ram_preview (end to end), the disk cache proxy key. Docs: roadmap (M4 done), parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:39:17 +07:00
bflatastic 4f7723a7b6 M13.31: Home size column test uses an unambiguous size (12.5 KB rounds to even)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:33:44 +07:00
BFlat 0f1cad8bbd
Merge pull request #53 from storytold/ci-packaging-lint
CI: fix Packaging lint (SC2015 in release.yml)
2026-10-05 13:52:21 +07:00
bflatastic a48de85015 CI: packaging lint passes again (wasm-bindgen install step as an if, not A && B || C)
actionlint's shellcheck flagged SC2015 on release.yml's Install wasm-bindgen CLI step, failing
the Packaging lint workflow on main and release since 2 October.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:50:37 +07:00
Brandon Thomas 0bb8baf4da
Merge pull request #52 from storytold/pr/release-0.2.0
Release: EffectCraft v0.2.0
2026-10-05 02:46:06 -04:00
Brandon Thomas 3f7250043d Release: EffectCraft v0.2.0
Bump the workspace version 0.1.1 -> 0.2.0 (cargo xtask version set 0.2.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:45:53 -04:00
BFlat fc0462a855
Merge pull request #51 from storytold/home-screen-ae
M13.30–M13.31: ArtCraft logo in the Tools bar; Home screen laid out like After Effects'
2026-10-05 12:52:18 +07:00
bflatastic d31094692e M13.31: Home screen laid out like After Effects'
- Home covers the whole workspace below the Tools bar (the panels wait behind it) instead of
  the Composition panel.
- Left rail: New Project (primary) and Open Project, the Home / Templates / Learn pages with
  icons, and the community links and ArtCraft apps at its foot.
- Home page: "Welcome to EffectCraft", quick-start tiles (New Composition, Open Demo Project,
  Import Footage, New from Template) and Recent as a table — thumbnail, name and folder,
  Opened (relative), Size, Kind — with a filter field, Clear list and an empty state.
- Theme tokens throughout (light themes follow); web_time for "now" (works on the web).
- Tests: formatters (opened / size / kind) and the new automation ids. Docs: parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:51:26 +07:00
bflatastic 2039688245 M13.30: the ArtCraft mark in the Tools bar, About and Home; VectorCraft sibling link
- paint_logo draws docs/brand/artcraft-mark.png (the README's / getartcraft.com's ArtCraft
  mark) as supplied, decoded once into a mipmapped texture, instead of a drawn placeholder.
- The "More ArtCraft apps" sibling and help.sibling named DrawCraft (storytold/drawcraft, which
  doesn't exist): it is VectorCraft (storytold/vectorcraft); provenance comments in the path
  crate follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:49:47 +07:00
BFlat 053c445465
Merge pull request #50 from storytold/m4-motion-blur-depth
M4.13: motion blur of collapsed precomps and of animated shape / text content
2026-10-05 12:36:07 +07:00
bflatastic 05188d036a M4.13: motion blur of collapsed precomps and of animated shape / text content
- Collapsed precomps: the renderer records the precomp layers a nested comp is drawn through;
  motion-blur sub-samples re-evaluate their transforms (when their motion blur is on) and map
  times down through each one, so the precomp layer's own motion blurs its nested layers. The
  containing (outermost) comp's shutter applies to collapsed layers. The GPU compositor gets
  this through Renderer::placement.
- Shape and text layers whose content changes within the shutter (compared by its cache key)
  draw their source at Samples Per Frame times and average them; the cache key covers every
  sub-sample's content.
- Tests: tests_motion_blur_depth (collapsed vs flat, the containing shutter, shape content vs
  the same move by Position); each fails without the change.
- Docs: roadmap, parity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:35:30 +07:00
BFlat 9e79271893
Merge pull request #10 from storybored379/pr/spatial-tangent-getter-names
Scripting: Property.keyInSpatialTangent / keyOutSpatialTangent under their AE names
2026-10-05 12:11:18 +07:00
BFlat 58dd94e212
Merge pull request #9 from storybored379/pr/hold-key-incoming-segment
Keyframes: a Hold key no longer eases the motion into it
2026-10-05 12:11:10 +07:00
BFlat 1e8bcb519a
Merge pull request #7 from storybored379/pr/comp-zero-frame-rate
Compositions: reject zero and negative frame rates instead of panicking
2026-10-05 12:11:02 +07:00
BFlat 26f577cb1a
Merge pull request #6 from storybored379/pr/expr-runtime-stack-leak
Expressions: keep the runtime usable after errors and property reads
2026-10-05 12:10:53 +07:00
BFlat 9b5652f686
Merge pull request #49 from storytold/timeline-comp-tabs
M3.15: Timeline tabs per open comp; Project double-click opens; tab drag insertion mark
2026-10-05 11:59:07 +07:00
bflatastic c7c90cfa74 M3.15: Timeline tabs per open comp; Project double-click opens; tab drag insertion mark
- The Timeline shows one tab per open comp (dock DocTab). Clicking a tab shows that comp; ×
  or middle-click closes that comp's Timeline (comp.close), not the panel. Opening a comp brings
  its viewer and Timeline tab to the front.
- Project panel: a double-click on an item's name opens it (a comp gets its own tab) instead of
  renaming; Enter and the context menu rename.
- Tab drags: over a tab strip an insertion mark shows where the tab lands, and it lands there
  (Layout::insert_tab), which also reorders a group's own tabs; side zones are unchanged.
- Tests: ui_tabs (double-click, tab switch / close, drag reorder and cross-group insert; an
  ignored mid-drag snapshot); the Timeline close test follows the new behaviour.
- Docs: parity, control protocol.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:58:19 +07:00
BFlat a008a904af
Merge pull request #48 from storytold/graph-auto-visible-keys
M5.15–M5.16: Timeline-visible J/K and Select All, Graph Editor Auto-Select Graph Type, Key Light eyedropper
2026-10-05 11:05:02 +07:00
bflatastic 73b2607c9a M5.16: Key Light: keyer eyedroppers pick from the effect input (effect.pickColor); "keylight" search
- effect.pickColor {effect, param|prop, x, y (layer px), average?}: the colour of the effect's
  input at a point, set on the parameter (one undo step). Keying effects' Effect Controls
  eyedroppers use it, since the shown frame is already keyed; Ctrl/Cmd+click averages 5x5.
- effects::aliases / name_matches: Effects & Presets and effect.list (MCP list_effects) find
  Key Light by After Effects' name ("Keylight (1.2)").
- End-to-end test: a green-screen plate with an uneven screen, spill and soft edges keyed via
  effect.apply "Keylight (1.2)", pick, Clip Black / White and a render; UI eyedropper test.
- Docs: parity, agents (green-screen recipe).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 11:04:10 +07:00
bflatastic 47f2a5a3e6 M5.15: J / K and Select All Keyframes use what the Timeline shows; Graph Editor Auto-Select Graph Type
- J / K (Go to Previous / Next Keyframe) and Ctrl+Alt+A (Select All Keyframes) act on what the
  Timeline shows, as in After Effects: the keys of its revealed properties, layer and comp
  markers and the work area (they used every property of the selected or all layers, revealed
  or not). The commands take `visible: [{layer, prop}]`, which the Timeline fills in; agents
  calling them without it keep the previous behaviour.
- Graph Editor ▸ Auto-Select Graph Type (an "Auto" button, on by default as in After Effects):
  the speed graph when every property shown is spatial (Position, Anchor Point…), else the
  value graph; choosing Value or Speed turns it off.
- Docs: parity, agents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:36:55 +07:00
BFlat c90fd35ff8
Merge pull request #40 from storytold/timeline-keyframes
Fix the periodic freeze on Windows; keyframe drag, copy/paste and Graph Editor behave like After Effects
2026-10-05 08:13:28 +07:00
bflatastic ef856d8f67 M5.14: docs: parity, agents and preferences cover the keyframe, clipboard and responsiveness fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:06:32 +07:00
bflatastic dfb0227468 M5.14: J / K stop at the work area; the Info panel shows a selected keyframe
- Go to Next / Previous Keyframe (K / J) also stops at the work area's start and end, as in
  After Effects.
- With one keyframe selected, the Info panel shows its property, time and value instead of
  "1 keyframes selected".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:05:03 +07:00
bflatastic 43bf2906d3 M5.13: keyframe paste: several copied layers paste onto as many layers in order; Paste Reversed pastes like Paste
- Keys copied from several layers and pasted onto as many selected layers go first to first,
  second to second, as in After Effects; every target used to receive every copied property
  (the later ones overwriting the earlier).
- Edit ▸ Paste Reversed Keyframes mirrors the clipboard over its span and pastes it through
  `keys.paste`, so it now follows the same rules as Paste: the layer's time stretch, the
  selected target property, Hold-only properties, value conversion and roving keys (its own
  simplified paste ignored them and kept existing keys' easing on a time collision).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 08:02:16 +07:00
bflatastic 9ea1f1325d M5.12: double-click a keyframe to edit its value; Select Equal / Previous / Following Keyframes; roving keys as dots
- Double-clicking a keyframe of a numeric property (or its context menu ▸ Edit Value…) opens
  the property's value dialog for that key, as in After Effects (it only moved the current
  time); other keys still move the current time to them.
- The keyframe context menu has Select Equal Keyframes, Select Previous Keyframes and Select
  Following Keyframes (`keys.selectEqual` / `selectPrevious` / `selectFollowing`).
- Roving keys draw as small dots in the Timeline, like After Effects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:58:03 +07:00
bflatastic daed03305a M5.11: Graph Editor: drags move every selected key, Shift keeps an axis, Ctrl / Ctrl+Alt clicks; drags and the transform box never merge or lose keys
- `keys.transform {fromStart}`: each step of a merged drag gives the whole transform since the
  drag started and applies to the keys as they were then. Keys squeezed onto one frame on the
  way (Alt-drag scaling, the transform box) are no longer merged for good, and a slow
  transform-box move, whose per-frame offsets rounded to nothing, now moves the keys.
- Dragging a key in the Graph Editor moves every selected key in time and value (it moved only
  the grabbed one), by how far the pointer went from where the button went down (the key no
  longer jumps under the pointer); Shift keeps the drag to the time or the value axis.
- Shift+click toggles a key in or out of the selection; Ctrl+click switches Linear ↔ Auto
  Bezier and Ctrl+Alt+click toggles Hold, as in the Timeline.
- The transform box and the Timeline's Alt-drag scale send whole-gesture values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:52:20 +07:00