0.2.0: polygon tool, mask editor preview, collapsible properties
- Polygon asset type with per-vertex animation: new sidebar button inserts a hexagon; vertex handles in the editor write per-keyframe `points` arrays that interpolate between keyframes (requires matching point counts). - Canvas mask preview: toggle in the zoom toolbar switches the stage between the editable ghost view and a rendered view that applies the actual masking via the shared canvas 2D path. - Properties panel is grouped into collapsible sections (Transform, Text, Appearance, Shadow, Mask) with per-section open/closed state persisted to localStorage. - Sidebar shape buttons are icon-only to reclaim width for the Polygon entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
03b1147a93
commit
a05396e734
12 files changed
+611
-173
No files matched your search
+36
-2
@@ -47,6 +47,14 @@ const ENGINE_SOURCE = `(function(){
|
||||
if(a.fill&&b.fill)out.fill=lerpColor(a.fill,b.fill,t);
|
||||
else if(b.fill)out.fill=b.fill;
|
||||
else if(a.fill)out.fill=a.fill;
|
||||
if(a.points&&b.points&&a.points.length===b.points.length){
|
||||
var lp=[];
|
||||
for(var lpi=0;lpi<a.points.length;lpi++){
|
||||
lp.push({x:lerp(a.points[lpi].x,b.points[lpi].x,t),y:lerp(a.points[lpi].y,b.points[lpi].y,t)});
|
||||
}
|
||||
out.points=lp;
|
||||
} else if(b.points) out.points=b.points;
|
||||
else if(a.points) out.points=a.points;
|
||||
return out;
|
||||
}
|
||||
function sample(layer,time){
|
||||
@@ -143,7 +151,7 @@ const ENGINE_SOURCE = `(function(){
|
||||
function drawMaskShape(ctxm,layer,time){
|
||||
var asset=assetMap[layer.assetId];
|
||||
if(!asset) return;
|
||||
if(asset.kind!=='rect' && asset.kind!=='circle' && asset.kind!=='text') return;
|
||||
if(asset.kind!=='rect' && asset.kind!=='circle' && asset.kind!=='text' && asset.kind!=='polygon') return;
|
||||
var p=sample(layer,time);
|
||||
ctxm.save();
|
||||
ctxm.translate(p.x,p.y);
|
||||
@@ -160,12 +168,23 @@ const ENGINE_SOURCE = `(function(){
|
||||
ctxm.beginPath();
|
||||
ctxm.arc(-mcOff,-mcOffY,asset.radius,0,Math.PI*2);
|
||||
ctxm.fill();
|
||||
} else {
|
||||
} else if(asset.kind==='text'){
|
||||
var mtOff=layer.offsetX!=null?layer.offsetX:0;
|
||||
var mtOffY=layer.offsetY!=null?layer.offsetY:0;
|
||||
ctxm.font=asset.fontSize+'px sans-serif';
|
||||
ctxm.textBaseline='top';
|
||||
ctxm.fillText(asset.text,-mtOff,-mtOffY);
|
||||
} else {
|
||||
var mpOffX=layer.offsetX!=null?layer.offsetX:0;
|
||||
var mpOffY=layer.offsetY!=null?layer.offsetY:0;
|
||||
var mpPts=p.points||asset.points||[];
|
||||
if(mpPts.length>=3){
|
||||
ctxm.beginPath();
|
||||
ctxm.moveTo(mpPts[0].x-mpOffX,mpPts[0].y-mpOffY);
|
||||
for(var mpI=1;mpI<mpPts.length;mpI++)ctxm.lineTo(mpPts[mpI].x-mpOffX,mpPts[mpI].y-mpOffY);
|
||||
ctxm.closePath();
|
||||
ctxm.fill();
|
||||
}
|
||||
}
|
||||
ctxm.restore();
|
||||
}
|
||||
@@ -217,6 +236,21 @@ const ENGINE_SOURCE = `(function(){
|
||||
ctx.font=asset.fontSize+'px sans-serif';
|
||||
ctx.textBaseline='top';
|
||||
ctx.fillText(asset.text,-tOffX,-tOffY);
|
||||
}else if(asset.kind==='polygon'){
|
||||
var pOffX=layer.offsetX!=null?layer.offsetX:0;
|
||||
var pOffY=layer.offsetY!=null?layer.offsetY:0;
|
||||
var pBlur=(p.blur!=null?p.blur:(asset.blur||0));
|
||||
var pPts=p.points||asset.points||[];
|
||||
if(pPts.length>=3){
|
||||
ctx.fillStyle=p.fill||asset.fill;
|
||||
if(pBlur>0) ctx.filter='blur('+pBlur+'px)';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(pPts[0].x-pOffX,pPts[0].y-pOffY);
|
||||
for(var pI=1;pI<pPts.length;pI++)ctx.lineTo(pPts[pI].x-pOffX,pPts[pI].y-pOffY);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.filter='none';
|
||||
}
|
||||
}else if(asset.kind==='image'){
|
||||
var img=imgs[asset.id];
|
||||
if(img){
|
||||
|
||||
Reference in new issue
Block a user