0.18.3: grouped clip tracks render, playhead is a snap magnet

- Fix mask-preview / frame-export dropping any group whose children
  are clip tracks. render.ts's old group branch only pulled Layer
  children out of group.childIds; new drawTrackGroup walks both
  layer AND clip-track ids and still applies group-local masks to
  the composite. Same fix mirrored into the HTML export runtime in
  exportPlayer.ts.
- Snapping magnet for the playhead: snapTime now includes
  store.playhead in its candidate list by default. Clip drags, trim
  handles, keyframe drags, marker drags and in/out brackets stick
  to the needle when snap is on. Scrub handlers pass
  ignorePlayhead:true so the needle doesn't stick to its own prior
  position during a scrub.
- Clip trim handles derive the snapped edge first and back out to
  trimStart/trimEnd, so the edge lands exactly on the magnet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-24 16:20:18 +02:00
1 parent 21cf6e9a12
commit 56cecabaae
6 files changed
+543 -32

No files matched your search

+45 -6
View File
@@ -1016,14 +1016,53 @@ const ENGINE_SOURCE = `(function(){
}
var grp=groupById2[id];
if(grp){
var gContent=[], gMasks=[];
// gContentIds holds layer OR clip-track child ids. Dropping
// clip tracks here used to make any group-with-only-clips
// invisible in the runtime (same bug drawTrackGroup fixes
// on the editor side).
var gContentIds=[], gMasks=[];
for(var ch=grp.childIds.length-1;ch>=0;ch--){
var cl0=layerById[grp.childIds[ch]];
if(!cl0) continue;
if(cl0.isMask) gMasks.push(cl0); else gContent.push(cl0);
var cid=grp.childIds[ch];
var cl0=layerById[cid];
if(cl0){
if(cl0.isMask) gMasks.push(cl0); else gContentIds.push(cid);
continue;
}
if(instByTrack[cid]) gContentIds.push(cid);
}
var drawChildInGroup=function(cid){
var lr=layerById[cid];
if(lr){ drawLayer(lr,time); return; }
var insts3=instByTrack[cid]||[];
for(var ti3=0;ti3<insts3.length;ti3++){
var inst4=insts3[ti3];
var clip4=clipById2[inst4.clipId];
if(!clip4) continue;
var tS4=inst4.trimStart||0;
var tE4=inst4.trimEnd||0;
var vis4=Math.max(0,clip4.duration-tS4-tE4);
var off4=time-inst4.startTime;
if(off4<0||off4>vis4) continue;
var lt4=tS4+off4;
var ix4=inst4.x||0, iy4=inst4.y||0;
var isx4=(inst4.scaleX==null?1:inst4.scaleX);
var isy4=(inst4.scaleY==null?1:inst4.scaleY);
var irot4=inst4.rotation||0;
var needsXf4=ix4!==0||iy4!==0||isx4!==1||isy4!==1||irot4!==0;
if(needsXf4){
ctx.save();
ctx.translate(ix4,iy4);
if(irot4!==0) ctx.rotate(irot4*Math.PI/180);
ctx.scale(isx4,isy4);
}
for(var cl4=clip4.layers.length-1;cl4>=0;cl4--){
drawLayer(clip4.layers[cl4],lt4);
}
if(needsXf4) ctx.restore();
}
};
if(!gMasks.length){
for(var gi3=0;gi3<gContent.length;gi3++) drawLayer(gContent[gi3],time);
for(var gi3=0;gi3<gContentIds.length;gi3++) drawChildInGroup(gContentIds[gi3]);
} else {
var mcanvas=document.createElement('canvas');
mcanvas.width=canvas.width; mcanvas.height=canvas.height;
@@ -1032,7 +1071,7 @@ const ENGINE_SOURCE = `(function(){
mctx.setTransform(tm.a,tm.b,tm.c,tm.d,tm.e,tm.f);
var savedCtx=ctx;
ctx=mctx;
for(var gi4=0;gi4<gContent.length;gi4++) drawLayer(gContent[gi4],time);
for(var gi4=0;gi4<gContentIds.length;gi4++) drawChildInGroup(gContentIds[gi4]);
for(var gm=0;gm<gMasks.length;gm++){
var mL=gMasks[gm];
mctx.save();