Commit Graph

38 Commits

Author SHA1 Message Date
Skyler Lehmkuhl 2cbc35f181 Mobile context-menu coverage + z-order
Hook up menu-bar actions that lacked a mobile affordance, and implement z-order.

- Stage/object long-press menu (stage.rs): add Paste (on clip, geometry, and
  empty stage) and Send to Back / Bring to Front on the selection.
- Timeline long-press menu (timeline.rs): the timeline had no mobile context
  menu — add one via manual long-press detection. Clip actions (Split,
  Duplicate, Cut, Copy, Paste, Delete) on a clip; animation actions (New/Blank
  keyframe, Add keyframe at playhead, Duplicate keyframe, Delete frame, Add
  motion/shape tween) on an empty lane. Gate the desktop right-click menu to
  !is_mobile so mobile shows only the long-press menu.
- Implement Send to Back / Bring to Front (were // TODO no-ops): new undoable
  ReorderClipInstancesAction in lightningbeam-core reorders the selected
  instances within their layer's clip_instances Vec (stacking order; last =
  on top; geometry stays flattened underneath). Wired via handle_menu_action.
2026-07-02 10:07:53 -04:00
Skyler Lehmkuhl fd582828c2 Add text layers
Introduce editable text layers: a resizable text box with editable text,
font size, color, font family, and alignment.

Core:
- New TextLayer/TextContent (text_layer.rs), wired into AnyLayer/LayerType
  and all the exhaustive match sites; structured so content can be keyframed
  later via content_at().
- fonts.rs: thread-local parley FontContext with three bundled fonts
  (Liberation Sans/Serif/Mono, SIL OFL), system-font enumeration consolidated
  to base families, document-embedded fonts, glyph/caret/selection geometry,
  and a background preloader for the picker fonts.
- Rendering via parley layout + Scene::draw_glyphs (renderer.rs); text
  composites through the vector path.
- Actions: CreateTextClipAction (vector-layer branch, undoable),
  SetTextContentAction, ResizeTextBoxAction.
- .beam font embedding: MediaKind::Font rows (content-hash dedupe) written on
  save and registered on load, with bundled-default fallback.
- VectorClip content bounds include text boxes so text-only clips are
  selectable/draggable.

Editor:
- Text tool: click empty/raster/video to create a top-level text layer, or a
  vector layer to create+enter a clip containing the text; click an existing
  box to edit it.
- Hybrid in-place editing: a hidden egui TextEdit drives input/IME/caret while
  the text and caret/selection render in Vello; empty just-created layers are
  removed on commit.
- Selection outline + 8 resize handles (re-wrap text) with hover cursors;
  factored the corner/edge resize-cursor mapping shared with the Transform tool.
- Info panel: edit text, size, color, alignment, box size, and a font-family
  picker that previews each entry in its own font (fonts preloaded in the
  background to avoid hitches).

Deps: add parley (git, pinned to match vello's peniko); bundle Liberation
fonts under lightningbeam-core/assets/fonts. Gitignore the local
.cargo/config.toml used to select a machine's ffmpeg.
2026-06-27 18:14:47 -04:00
Skyler Lehmkuhl 5869e3ced1 raster: info-panel size + "Layer to document size" (scale/expand-crop)
Resizing the document leaves raster layers as-is (canvas keeps its old pixel size,
anchored top-left). To reconcile, the info panel now shows the active raster
layer's canvas size — driven by the *active* layer, not selection focus, since
painting doesn't focus the layer — and, when it differs from the document, a
Scale / Expand-Crop toggle + a "Layer to document size" button.

- RasterKeyframe::resize_to(w, h, mode): always applies the new declared size;
  resamples (Lanczos3) when Scale, top-left pad/trim when Canvas, and only touches
  the buffer when pixels are resident (a blank canvas just takes the new size).
  Sets texture_dirty so the stage's dirty-scan refreshes the GPU texture.
- ResizeRasterLayerAction resizes every keyframe with undo, holding a (read-only)
  RasterStore so paged-out keyframes are loaded one at a time rather than bulk.

Resized keyframes stay resident + dirty and persist on the next full save (no
incremental store write to page them back out). Scale is lossy/compounds;
Expand-Crop is lossless.
2026-06-26 15:40:23 -04:00
Skyler Lehmkuhl a1acecf396 Implement shape tweens (same-topology lerp)
`tween_after == Shape` was stored on keyframes but never read. Now the
render path morphs geometry across a shape-tween span:

- VectorGraph::interpolated(other, t): same-topology lerp of vertex
  positions, edge curves, stroke widths and stroke/fill colours. Returns
  None when topology differs (counts, deleted flags, edge endpoints, fill
  boundaries), so the caller holds the source keyframe.
- VectorLayer::tweened_graph_at(time): returns an owned morphed graph for
  a shape-tween span whose two keyframes share topology, else borrows the
  held keyframe. Editing still uses graph_at_time (the held keyframe).
- Renderer (Vello + CPU paths) renders via tweened_graph_at.
- SetTweenAction + wired the previously-stubbed "Add Shape Tween" menu.

The typical workflow — keyframe, duplicate it (same topology), move
vertices, Add Shape Tween — now morphs between the two. Non-matching
topology falls back to a hold.
2026-06-21 15:46:53 -04:00
Skyler Lehmkuhl 0d253d9629 Motion tweens: implement Group + Convert to Movie Clip for DCEL geometry
Both actions were DCEL-stubs (no-ops). They now extract the selected geometry from a
vector layer's active keyframe into a new VectorClip (group vs movie clip) and place a
ClipInstance in its place (identity transform → renders where the geometry was), which
the existing transform-animation system can motion-tween.

- Shared `clip_from_geometry::extract_geometry_to_clip` (+ undo) does the work; the
  actions are thin wrappers. Undo snapshots the source graph + removes the clip/instance.
- `extract_subgraph` now DERIVES shared-fill boundary edges internally (an inside edge
  still used by a non-extracted fill must be duplicated, not moved) and unions them with
  the caller's `explicit_boundary` — so a plain geometry selection needs no boundary
  analysis (the selection already includes fill boundary edges via `select_fill`). The
  region-select caller keeps passing its lasso boundary.
- Handlers: Group / Convert to Movie Clip on a geometry selection now build these actions
  from `selected_fills`/`selected_edges`.

Next: shape tweens (same-topology lerp).
2026-06-21 09:12:46 -04:00
Skyler Lehmkuhl 6fc3a131a6 Phase 3.5a: SetImageFillAction + Info-Panel image-fill picker
- SetImageFillAction (core): set/clear `image_fill` on the selected VectorGraph fills,
  with per-fill undo (mirrors SetFillPaintAction). Image takes render priority; clearing
  reveals the colour/gradient underneath.
- Info Panel Shape section: an "Image:" combo listing the document's image assets (+ None)
  for the selected fill(s), showing the current assignment. Assign/clear pushes the action.

This lets an existing shape be given (or cleared of) an image fill, complementing the
import/drop placement. Next: 3.5b — persist image assets in the .beam container.
2026-06-21 00:30:01 -04:00
Skyler Lehmkuhl 2cbaf67583 Raster keyframe timeline UI: display + explicit creation + no lazy create
Make raster layers behave like vector on the timeline.

- Timeline: draw a diamond per `RasterKeyframe` (mirrors the vector keyframe block).
- New Keyframe (K / menu): on a raster layer, insert a BLANK cel at the playhead via
  a new undoable `AddRasterKeyframeAction` (+ `RasterLayer::insert_blank_keyframe_at`
  / `remove_keyframe`). Vector path unchanged.
- Stop lazy creation: paint tools now edit the ACTIVE keyframe (at-or-before the
  playhead) instead of creating one. The brush captures the active keyframe's exact
  time; `RasterStroke`/`RasterFillAction` resolve via `keyframe_at_mut` (error if
  none); the tool-site `ensure_keyframe_at` blocks (brush/fill/bucket/wand/quick-
  select/floating-lift) are removed — each read already bails when no keyframe exists.
  New layers still seed a keyframe at the playhead, so there's normally one to paint
  into; painting before the first keyframe is now a no-op (as intended).

Next: onion skinning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 22:22:02 -04:00
Skyler Lehmkuhl aae51e3b3c Phase 3d: dirty-rect diffs for raster undo (bound undo-history RAM)
`RasterStrokeAction`/`RasterFillAction` stored the whole before+after RGBA frame
(~16 MB/action at 1080p → up to ~1.6 GB at the 100-action cap). They now store a
`RasterDiff` — only the changed bounding box's pixels before and after — computed
once in `new()` from the full buffers, which are then dropped. A brush dab shrinks
from ~16 MB to tens of KB; a full-canvas fill is unchanged (its bbox is the frame).

Paging interaction: a diff overwrites just the bbox, so the keyframe's pixels must
be resident when undo/redo applies. A clean evicted frame's container bytes equal
its current logical state, so the editor faults the target frame in (synchronously)
before undo/redo via a new `Action::raster_resident_hint` + `peek_undo/redo_raster_hint`.
Dirty frames are never evicted, so they're already resident. If a base is somehow
not resident the apply is skipped (logged), never resized-and-corrupted.

Unit tests cover exact before/after round-trip, blank-first-stroke, no-op, and the
non-resident-base skip.
2026-06-20 21:31:38 -04:00
Skyler Lehmkuhl cfb8e4462b All events now have three time references for seconds, measures/beats, frames 2026-03-30 10:15:55 -04:00
Skyler Lehmkuhl 434b488a4c Merge branch 'rust-ui' of https://git.skyler.io/skyler/Lightningbeam into rust-ui 2026-03-22 18:16:25 -04:00
Skyler Lehmkuhl f16e651610 work on vector graph 2026-03-22 18:16:17 -04:00
Skyler Lehmkuhl 6b6ae230a1 Add pitch bend support 2026-03-18 23:11:24 -04:00
Skyler Lehmkuhl 0d2609c064 work on raster tools 2026-03-07 16:55:38 -05:00
Skyler Lehmkuhl 1e80b1bc77 Paint bucket 2026-03-07 02:53:47 -05:00
Skyler Lehmkuhl 1c3f794958 Merge remote and fix color space 2026-03-01 15:50:53 -05:00
Skyler Lehmkuhl 1c7256a12e Add raster layers 2026-03-01 13:16:49 -05:00
Skyler Lehmkuhl 516960062a Drag layers to reorder 2026-03-01 11:54:41 -05:00
Skyler Lehmkuhl 38831948ac group layers manually 2026-03-01 11:01:51 -05:00
Skyler Lehmkuhl b87e4325c2 use group layers instead of linked tracks 2026-03-01 09:00:55 -05:00
Skyler Lehmkuhl 05966ed271 rest of DCEL migration 2026-02-24 11:41:10 -05:00
Skyler Lehmkuhl 99f8dcfcf4 Change vector drawing primitive from shape to doubly-connected edge graph 2026-02-23 21:29:58 -05:00
Skyler Lehmkuhl 2222e68a3e Work on region select 2026-02-21 06:04:54 -05:00
Skyler Lehmkuhl 1892f970c4 Initial work on movie clips 2026-02-21 00:54:38 -05:00
Skyler Lehmkuhl 3ba6dcb3d2 Group shapes 2026-02-20 11:13:56 -05:00
Skyler Lehmkuhl 042dd50db3 Add clip looping 2026-02-20 02:48:44 -05:00
Skyler Lehmkuhl c6a8b944e5 Add copy and paste 2026-02-15 04:38:42 -05:00
Skyler Lehmkuhl c11dab928c Add spectrogram 2026-02-12 18:37:34 -05:00
Skyler Lehmkuhl b19f66e648 add folders to asset library 2025-12-30 00:45:19 -05:00
Skyler Lehmkuhl ffb53884b0 initial vector editing 2025-12-22 18:34:01 -05:00
Skyler Lehmkuhl 1bf1213e3f split clips for editing 2025-12-09 18:45:57 -05:00
Skyler Lehmkuhl 7eb61ab0a8 start work on compositing, effects 2025-12-08 09:30:22 -05:00
Skyler Lehmkuhl c943f7bfe6 Add info panel 2025-11-30 10:01:10 -05:00
Skyler Lehmkuhl 4d1e052ee7 Add asset pane 2025-11-30 06:54:53 -05:00
Skyler Lehmkuhl f9761b8af3 Add layer controls, fix dragging for clips 2025-11-29 12:33:17 -05:00
Skyler Lehmkuhl bbeb85b3a3 Clips in timeline 2025-11-28 05:53:11 -05:00
Skyler Lehmkuhl e1d9514472 Paint bucket 2025-11-19 01:47:37 -05:00
Skyler Lehmkuhl 9204308033 Transform shapes 2025-11-18 05:08:33 -05:00
Skyler Lehmkuhl 67724c944c Select and move shapes 2025-11-18 00:22:28 -05:00