macro-clips slice 4d: button canvas runtime + pointer handling
- ButtonInstance gains optional x/y; new instances default to stage center and setButtonInstancePosition updates them - useButtonRuntime composable holds per-instance state/disabled overrides (runtime-only, not persisted). getState layers the runtime disabled override on top of the persisted initial flag. - store.actionRuntime's setButtonState/setButtonDisabled now drive useButtonRuntime instead of mutating the scene, so script-triggered state changes stay ephemeral - CanvasStage renders each visible (playhead >= startTime) button instance inside a <v-group> at (bi.x, bi.y), drawing the current state's layer stack with draggable/listening disabled plus a transparent hit-rect sized to the clip's hitArea or the bbox of the first rect/circle layer. The group wires mouseenter/leave/ down/up into the state machine and fires onClick via evalAction. - exportPlayer runtime mirrors the same thing: getEffectiveButtonState, per-frame buttonHits array, canvas-level mousemove/down/up handlers do their own hit testing in scene space. RUNTIME_VERSION bumped to 5. - tests cover useButtonRuntime defaults/overrides and the store.actionRuntime → runtime-state roundtrip via label resolution. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
024883943b
commit
f61f90d613
6 files changed
+412
-8
No files matched your search
+123
-1
@@ -1,6 +1,6 @@
|
||||
import type { Scene } from '@/types/scene'
|
||||
|
||||
const RUNTIME_VERSION = '4'
|
||||
const RUNTIME_VERSION = '5'
|
||||
|
||||
const ENGINE_SOURCE = `(function(){
|
||||
if (window.AnimatrPlayer && window.AnimatrPlayer.__v === '${RUNTIME_VERSION}') return;
|
||||
@@ -212,6 +212,79 @@ const ENGINE_SOURCE = `(function(){
|
||||
for(var bri=0;bri<bis.length;bri++) if(bis[bri].id===ref) return ref;
|
||||
return runtime.buttonLabels[ref]||null;
|
||||
}
|
||||
var buttonHits=[]; // refreshed per draw()
|
||||
function getEffectiveButtonState(bi){
|
||||
var ovD=runtime.buttonDisabled[bi.id];
|
||||
var isDis=ovD===undefined?!!bi.disabled:ovD;
|
||||
if(isDis) return 'disabled';
|
||||
return runtime.buttonStates[bi.id]||'normal';
|
||||
}
|
||||
function setButtonState(id, st){
|
||||
if(st==='normal') delete runtime.buttonStates[id];
|
||||
else runtime.buttonStates[id]=st;
|
||||
}
|
||||
function pointerToSceneXY(e){
|
||||
var rc=canvas.getBoundingClientRect();
|
||||
var lw=scene.canvas.width, lh=scene.canvas.height;
|
||||
var cw=size.w, ch=size.h;
|
||||
if(!cw||!ch||!rc.width||!rc.height) return null;
|
||||
var sc=Math.min(cw/lw, ch/lh);
|
||||
var ox=(cw-lw*sc)/2, oy=(ch-lh*sc)/2;
|
||||
var cx=(e.clientX-rc.left)/rc.width*cw;
|
||||
var cy=(e.clientY-rc.top)/rc.height*ch;
|
||||
return { x:(cx-ox)/sc, y:(cy-oy)/sc };
|
||||
}
|
||||
function hitButtonAt(p){
|
||||
for(var i=buttonHits.length-1; i>=0; i--){
|
||||
var h=buttonHits[i];
|
||||
var lx=p.x-(h.bi.x||0), ly=p.y-(h.bi.y||0);
|
||||
if(lx>=h.hit.left && lx<=h.hit.right && ly>=h.hit.top && ly<=h.hit.bottom) return h;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function onStageMove(e){
|
||||
var p=pointerToSceneXY(e);
|
||||
if(!p) return;
|
||||
var hit=hitButtonAt(p);
|
||||
for(var i=0;i<buttonHits.length;i++){
|
||||
var bh=buttonHits[i];
|
||||
var isDis=getEffectiveButtonState(bh.bi)==='disabled';
|
||||
if(isDis) continue;
|
||||
if(hit && hit.bi.id===bh.bi.id){
|
||||
var cur=getEffectiveButtonState(bh.bi);
|
||||
if(cur==='normal') setButtonState(bh.bi.id,'hover');
|
||||
} else {
|
||||
var cur2=getEffectiveButtonState(bh.bi);
|
||||
if(cur2==='hover'||cur2==='pressed') setButtonState(bh.bi.id,'normal');
|
||||
}
|
||||
}
|
||||
}
|
||||
function onStageDown(e){
|
||||
var p=pointerToSceneXY(e);
|
||||
if(!p) return;
|
||||
var hit=hitButtonAt(p);
|
||||
if(!hit) return;
|
||||
if(getEffectiveButtonState(hit.bi)==='disabled') return;
|
||||
setButtonState(hit.bi.id,'pressed');
|
||||
}
|
||||
function onStageUp(e){
|
||||
var p=pointerToSceneXY(e);
|
||||
if(!p) return;
|
||||
for(var i=0;i<buttonHits.length;i++){
|
||||
var bh=buttonHits[i];
|
||||
if(getEffectiveButtonState(bh.bi)!=='pressed') continue;
|
||||
var lx=p.x-(bh.bi.x||0), ly=p.y-(bh.bi.y||0);
|
||||
var inside=lx>=bh.hit.left&&lx<=bh.hit.right&&ly>=bh.hit.top&&ly<=bh.hit.bottom;
|
||||
setButtonState(bh.bi.id, inside?'hover':'normal');
|
||||
if(inside){
|
||||
var acts=bh.bi.onClick||[];
|
||||
for(var ai=0; ai<acts.length; ai++) evalAction(acts[ai]);
|
||||
}
|
||||
}
|
||||
}
|
||||
canvas.addEventListener('mousemove', onStageMove);
|
||||
canvas.addEventListener('mousedown', onStageDown);
|
||||
canvas.addEventListener('mouseup', onStageUp);
|
||||
var BUTTON_STATES=['normal','hover','pressed','disabled'];
|
||||
function parseScriptSource(source){
|
||||
var outActs=[];
|
||||
@@ -646,6 +719,55 @@ const ENGINE_SOURCE = `(function(){
|
||||
ctx.drawImage(topOff,0,0);
|
||||
ctx.restore();
|
||||
}
|
||||
// Draw button instances on top of everything else, and record their
|
||||
// scene-space hit rectangles for pointer handling.
|
||||
buttonHits.length=0;
|
||||
var bInsts=scene.buttonInstances||[];
|
||||
var bClips=scene.buttonClips||[];
|
||||
var bClipById={};
|
||||
for(var bcI=0;bcI<bClips.length;bcI++) bClipById[bClips[bcI].id]=bClips[bcI];
|
||||
for(var biI=0;biI<bInsts.length;biI++){
|
||||
var bi=bInsts[biI];
|
||||
if(time<bi.startTime) continue;
|
||||
var bclp=bClipById[bi.buttonClipId];
|
||||
if(!bclp) continue;
|
||||
var bst=getEffectiveButtonState(bi);
|
||||
var bStack=(bclp.states&&bclp.states[bst])||[];
|
||||
var bx=bi.x||0, by=bi.y||0;
|
||||
ctx.save();
|
||||
ctx.translate(bx, by);
|
||||
for(var blI=0; blI<bStack.length; blI++) drawLayer(bStack[blI], 0);
|
||||
ctx.restore();
|
||||
// Hit rect: explicit hitArea on clip, else bbox of first rect/circle
|
||||
// layer in the normal state.
|
||||
var hit=null;
|
||||
if(bclp.hitArea){
|
||||
var ox2=bclp.hitArea.offsetX==null?(bclp.hitArea.width/2):bclp.hitArea.offsetX;
|
||||
var oy2=bclp.hitArea.offsetY==null?(bclp.hitArea.height/2):bclp.hitArea.offsetY;
|
||||
hit={left:-ox2, top:-oy2, right:bclp.hitArea.width-ox2, bottom:bclp.hitArea.height-oy2};
|
||||
}
|
||||
if(!hit){
|
||||
var normalLs=(bclp.states&&bclp.states.normal)||[];
|
||||
for(var nli=0; nli<normalLs.length; nli++){
|
||||
var nl=normalLs[nli];
|
||||
var na=assetMap[nl.assetId];
|
||||
if(!na) continue;
|
||||
if(na.kind==='rect'||na.kind==='image'){
|
||||
var rox=nl.offsetX==null?na.width/2:nl.offsetX;
|
||||
var roy=nl.offsetY==null?na.height/2:nl.offsetY;
|
||||
hit={left:-rox, top:-roy, right:na.width-rox, bottom:na.height-roy};
|
||||
break;
|
||||
}
|
||||
if(na.kind==='circle'){
|
||||
var cox=nl.offsetX||0, coy=nl.offsetY||0;
|
||||
hit={left:cox-na.radius, top:coy-na.radius, right:cox+na.radius, bottom:coy+na.radius};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!hit) hit={left:-60, top:-20, right:60, bottom:20};
|
||||
buttonHits.push({bi:bi, hit:hit});
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
loadImages(scene.assets).then(function(loaded){
|
||||
|
||||
Reference in new issue
Block a user