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:
1 parent
21cf6e9a12
commit
56cecabaae
6 files changed
+543
-32
No files matched your search
+45
-6
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user