diff --git a/app/scripts/CatLab/Easelbone/EaselJS/DisplayObjects/Placeholder.js b/app/scripts/CatLab/Easelbone/EaselJS/DisplayObjects/Placeholder.js index 86a175a..900c2e9 100644 --- a/app/scripts/CatLab/Easelbone/EaselJS/DisplayObjects/Placeholder.js +++ b/app/scripts/CatLab/Easelbone/EaselJS/DisplayObjects/Placeholder.js @@ -105,6 +105,14 @@ define ( return; } + // If this placeholder has been pinned (Pinner.pinToTop moved it + // into a pin layer), don't drag it back next to the source + // element -- that would rip it out of the pin layer every tick + // and defeat the pin. + if (innerPlaceholder._pinnedToTop) { + return; + } + // Fast path: verify the cached position directly instead of // scanning the parent's children twice with getChildIndex. var siblings = element.parent.children; diff --git a/app/scripts/CatLab/Easelbone/EaselJS/Pinner.js b/app/scripts/CatLab/Easelbone/EaselJS/Pinner.js index 4bb8c7f..9fa0250 100644 --- a/app/scripts/CatLab/Easelbone/EaselJS/Pinner.js +++ b/app/scripts/CatLab/Easelbone/EaselJS/Pinner.js @@ -92,6 +92,13 @@ define( ensureContainer(stage).addChild(wrapper); wrapper.addChild(obj); // reparents obj out of anchor; obj's own transform/regX/regY untouched + // Flag so other machinery that manages an object's place in the + // display list (e.g. Placeholder.updateZIndex, which re-inserts + // an inner placeholder next to its source element every tick) + // can leave a pinned object where the Pinner put it instead of + // dragging it back and defeating the pin. + obj._pinnedToTop = true; + stage._pinnedElements.push(record); // Immediate sync: correctly placed even without a running tick. @@ -161,6 +168,7 @@ define( }, _restore: function (record) { + record.obj._pinnedToTop = false; if (record.obj.parent) { record.obj.parent.removeChild(record.obj); } @@ -184,16 +192,59 @@ define( return; } - // Carry the anchor's bounds so bounds-sizing children (e.g. Fill) size correctly. - var bounds = record.anchor.getBounds(); - if (bounds) { + // Positioning needs only the concatenated matrices. Reading + // them walks the anchor/container up to the stage, which can + // throw (or return null) while the tree is still being built + // (e.g. a pin created from a deferred tick during screen init). + // If so, leave the pin where it is and place it on a later sync. + var anchorMatrix; + var containerMatrix; + try { + anchorMatrix = record.anchor.getConcatenatedMatrix(); + containerMatrix = container.getConcatenatedMatrix(); + } catch (e) { + return; + } + + if (!anchorMatrix || !containerMatrix) { + return; + } + + // The anchor's bounds are OPTIONAL -- only used so bounds-sizing + // children (e.g. a Fill) can match the anchor's box. getBounds() + // walks the anchor's *subtree* and can throw when a descendant + // has incomplete bounds; that must NOT block positioning, so + // guard it separately and just skip the bounds carry on failure. + // Only touch bounds when they actually change. + var bounds = null; + try { + bounds = record.anchor.getBounds(); + } catch (e) { + bounds = null; + } + if (bounds && (record._bw !== bounds.width || record._bh !== bounds.height)) { + record._bw = bounds.width; + record._bh = bounds.height; wrapper.setBounds(0, 0, bounds.width, bounds.height); } // Place the wrapper in the anchor's coordinate space: // wrapperLocal = pinContainerConcatenated^-1 * anchorConcatenated - var local = container.getConcatenatedMatrix().invert() - .appendMatrix(record.anchor.getConcatenatedMatrix()); + var local = containerMatrix.invert().appendMatrix(anchorMatrix); + + // Short-circuit: if the resulting transform is bit-identical to + // last frame (a static QR on a still screen), skip the decompose + // and the property writes. The matrix reads above are the same + // inputs -> same outputs, so an exact compare is safe. + var m = record._m || (record._m = { a: 0, b: 0, c: 0, d: 0, tx: 0, ty: 0 }); + if ( + m.a === local.a && m.b === local.b && m.c === local.c && + m.d === local.d && m.tx === local.tx && m.ty === local.ty + ) { + return; + } + m.a = local.a; m.b = local.b; m.c = local.c; + m.d = local.d; m.tx = local.tx; m.ty = local.ty; var props = local.decompose(Pinner._props); wrapper.x = props.x; diff --git a/dist/scripts/easelbone.js b/dist/scripts/easelbone.js index 0325f6e..5b4ffed 100644 --- a/dist/scripts/easelbone.js +++ b/dist/scripts/easelbone.js @@ -3,12 +3,12 @@ define("CatLab/Easelbone/Utilities/Loader",["preloadjs","easeljs","CatLab/Easelb define("CatLab/Easelbone/Utilities/GlobalProperties",["backbone"],(function(t){return new(t.Model.extend({initialize:function(){this.set({width:800,height:600,font:"sans-serif",textColor:"white"})},getWidth:function(){return this.get("width")},getHeight:function(){return this.get("height")},getDefaultFont:function(){return this.get("font")},getDefaultTextColor:function(){return this.get("textColor")},ifUndefined:function(t,e){return null!=t?t:e}}))})); define("CatLab/Easelbone/Utilities/CustomAttributes",[],(function(){"use strict";return["textAlign","textColor","textFont"]})); define("CatLab/Easelbone/Utilities/DirtyFlag",[],(function(){"use strict";var i=!1,t=!1;return{invalidate:function(){i=!0},consume:function(){var t=i;return i=!1,t},installMovieClipHook:function(e){if(!t&&void 0!==e.MovieClip){t=!0;var n=e.MovieClip.prototype._tick;e.MovieClip.prototype._tick=function(t){var e=this.currentFrame;n.call(this,t),this.currentFrame!==e&&(i=!0)}}}}})); -define("CatLab/Easelbone/EaselJS/DisplayObjects/Placeholder",["easeljs","CatLab/Easelbone/Utilities/CustomAttributes","CatLab/Easelbone/Utilities/DirtyFlag"],(function(i,e,t){var n=function(i){void 0!==i&&(this.initialize(),this.initializePlaceholder(i))};return(n.prototype=new i.Container).initializePlaceholder=function(n){if(n.easelPlaceholderInitialized)console.error("Element is already initialized as placeholder.",n);else{n.easelPlaceholderInitialized=!0;var a,l,s,d=this,r=null,h=null,o=-1;if(e.forEach(function(i){Object.defineProperty(this,i,{get:function(){if(void 0!==n[i])return n[i]},set:function(e){console.log("setting",i,e)}})}.bind(this)),d.filters=n.filters,n.filters=[],n.uncache(),n.original_draw=n.draw,n.original_tick=n._tick,n.draw=function(i,e){return this.updateBounds(),n.original_draw.apply(n,arguments)},this.hasBoundsChanged=function(){var i=this.getBounds();return!(!i||i.width===r&&i.height===h||(r=i.width,h=i.height,0))},n._tick=function(){return n.parent&&n.parent.children&&this.updateZIndex(),n.original_tick.apply(n,arguments)},n.updateZIndex=function(){if(n.children){var i=n.parent.children;o>0&&i[o]===d&&i[o-1]===n||(l=n.parent.getChildIndex(d),(s=n.parent.getChildIndex(n))+1!==l&&(n.parent.addChildAt(d,s+1),t.invalidate()),o=s+1)}},n.updateBounds=function(){d.setBounds(0,0,Math.ceil(100*this.scaleX),Math.ceil(100*this.scaleY)),d.x=this.x-this.regX*this.scaleX,d.y=this.y-this.regY*this.scaleY,d.rotation=this.rotation,d.hasBoundsChanged()&&(this.mask?d.mask=this.mask:this.originalMask&&(d.mask=this.originalMask),this.mask=!1,a=new i.Event("bounds:change"),d.dispatchEvent(a))},n.children)for(var c=0;c0&&i[o]===d&&i[o-1]===n||(l=n.parent.getChildIndex(d),(s=n.parent.getChildIndex(n))+1!==l&&(n.parent.addChildAt(d,s+1),t.invalidate()),o=s+1)}},n.updateBounds=function(){d.setBounds(0,0,Math.ceil(100*this.scaleX),Math.ceil(100*this.scaleY)),d.x=this.x-this.regX*this.scaleX,d.y=this.y-this.regY*this.scaleY,d.rotation=this.rotation,d.hasBoundsChanged()&&(this.mask?d.mask=this.mask:this.originalMask&&(d.mask=this.originalMask),this.mask=!1,a=new i.Event("bounds:change"),d.dispatchEvent(a))},n.children)for(var c=0;c0&&(l=this.findFromNames(_.join("."),l))}.bind(this)),l.length>0)return l;return l},_.buildNamedChildMap=function(e){if(void 0===e._mh_named_children_map){if(e._mh_named_children_map={},e._mh_deep_named_children_map={},e._mh_timeline_label_map={},e.timeline)for(var i=e.timeline.getLabels(),n=0;n0)},_.pause=function(e){e.timeline&&(e.timeline._was_paused=e.timeline.paused,e.timeline.paused=!0),this.forEachNamedChild(e,function(e){this.pause(e)}.bind(this))},_.resume=function(e){e.timeline&&(e.timeline.paused=e.timeline._was_paused,delete e.timeline._was_paused),this.forEachNamedChild(e,function(e){this.resume(e)}.bind(this))},_.countLabeledFrames=function(e,i){this.buildNamedChildMap(i);var n=0;return i._mh_timeline_label_map&&i._mh_timeline_label_map[e]&&(n+=i._mh_timeline_label_map[e].length),n},_.jumpToFrame=function(e,i){var a=[];return i._mh_timeline_label_map&&i._mh_timeline_label_map[e]&&i._mh_timeline_label_map[e].forEach(function(i){a.push(this._jumpToFrameUntilFinished(e,i))}.bind(this)),n.when.apply(this,a)},_._jumpToFrameUntilFinished=function(e,i){var a=new n;return i.parent?(i.gotoAndPlay(e),i.timeline.on("complete",(function(){a.resolve()}))):a.resolve(),a.promise()},_.attach=function(e){var i=this,n=e.MovieClip.prototype;n.forEachNamedChild=function(e){return i.forEachNamedChild(this,e)},n.findNamedChildren=function(e){return i.findFromNames(e,this)},n.findPlaceholders=function(e){return i.findPlaceholders(e,this)},n.applySpriteFilters=function(e){return i.applySpriteFilters(e,this)},n.hasLabeledFrame=function(e){return i.hasLabeledFrame(e,this)},n.jumpToFrame=function(e){return i.jumpToFrame(e,this)}},new a})); define("CatLab/Easelbone/Views/Base",["backbone","easeljs","CatLab/Easelbone/Utilities/GlobalProperties","CatLab/Easelbone/Utilities/MovieClipHelper","CatLab/Easelbone/Utilities/Deferred"],(function(e,t,i,r,n){"use strict";return e.View.extend({el:"div",easelScreen:null,setRootView:function(e){this.set("root",e)},getWidth:function(){return this.getStage().canvas.width},getHeight:function(){return this.getStage().canvas.height},getStage:function(){return void 0!==this.el.stage?this.el.stage:void 0!==this.el.getStage?this.el.getStage():void 0},setScreen:function(e){return this.easelScreen=e,this},getScreen:function(){return this.easelScreen},hasLabeledFrame:function(e,t){if(void 0===t&&(t=this.easelScreen),!t)throw new Error("hasLabeledFrame requires a screen to be set or a container to be provided.");return r.hasLabeledFrame(e,t)},jumpToFrame:function(e,t){if(void 0===t&&(t=this.easelScreen)&&!t.parent){var i=new n;return this._pendingFrameJumps||(this._pendingFrameJumps=[]),this._pendingFrameJumps.push({label:e,deferred:i}),i.promise()}if(!t)throw new Error("hasLabeledFrame requires a screen to be set or a container to be provided.");return r.jumpToFrame(e,t)},_replayPendingFrameJumps:function(){if(this._pendingFrameJumps){var e=this._pendingFrameJumps;this._pendingFrameJumps=null,e.forEach(function(e){this.jumpToFrame(e.label).done((function(){e.deferred.resolve()}))}.bind(this))}},pause:function(e){if(void 0===e&&(e=this.easelScreen),!e)throw new Error("pause requires a screen to be set or a container to be provided.");return r.pause(this.easelScreen)},resume:function(e){if(void 0===e&&(e=this.easelScreen),!e)throw new Error("resume requires a screen to be set or a container to be provided.");return r.resume(this.easelScreen)},scale:function(e){var t=this.getScale();e.scaleX=t.x,e.scaleY=t.y},getScale:function(e,t,r){null==e&&(e=i.getWidth()),null==t&&(t=i.getHeight()),void 0===r&&(r=!1);var n=this.getWidth()/e,a=this.getHeight()/t,s=r?Math.max(n,a):Math.min(n,a);return{x:s,y:s}},addCenter:function(e,r,n,a,s){null==r&&(r=i.getWidth()),null==n&&(n=i.getHeight()),null==s&&(s=1);var h=this.getScale(r,n,a),o=!0;if(this.getWidth()===r&&this.getHeight()===n&&(o=!1),o&&(h.x=h.x*s,h.y=h.y*s,e.x=(this.getWidth()-r*h.x)/2,e.y=(this.getHeight()-n*h.y)/2,e.scaleX=h.x,e.scaleY=h.y),this.el.addChild(e),o){var l=new t.Graphics;e.x>=1&&(l.beginFill(this.getBackground()).drawRect(0,0,Math.ceil(e.x),this.getHeight()),l.beginFill(this.getBackground()).drawRect(this.getWidth()-Math.ceil(e.x),0,Math.ceil(e.x),this.getHeight())),e.y>=1&&(l.beginFill(this.getBackground()).drawRect(0,0,this.getWidth(),Math.ceil(e.y)),l.beginFill(this.getBackground()).drawRect(0,this.getHeight()-Math.ceil(e.y),this.getWidth(),Math.ceil(e.y)));var d=new t.Shape(l);this.el.addChild(d)}},clear:function(){this.el.removeAllChildren();var e=new t.Graphics;e.beginFill(this.getBackground()).drawRect(0,0,this.getWidth(),this.getHeight());var i=new t.Shape(e);this.el.addChild(i)},getBackground:function(){return"#000000"},render:function(){if(this.easelScreen)return this.addCenter(this.easelScreen),this._replayPendingFrameJumps(),this;var e=new t.Container;e.setBounds(0,0,this.getWidth(),this.getHeight());var i=new t.BigText("easelbone view: no screen set and render function was not overridden...","Arial","#000000");e.addChild(i),this.el.addChild(e)},findPlaceholders:function(e,t){return void 0===t&&(t=[],this.easelScreen&&t.push(this.easelScreen)),r.findPlaceholders(e,t)},findTextPlaceholders:function(e,t){return this.findPlaceholdersPreferPostfix(e,t,"text")},findPlaceholdersPreferPostfix:function(e,t,i){var r=[];return Array.isArray(e)?e.forEach((function(e){r.push(e+".text"),r.push(e)})):(r.push(e+".text"),r.push(e)),this.findPlaceholders(r,t)},findFromNames:function(e,t){return void 0===t&&(t=[],this.easelScreen&&t.push(this.easelScreen)),r.findFromNames(e,t)},findFromNameInContainer:function(e,t,i){return r.findFromNameInContainer(e,t,i)},forEachNamedChild:function(e,t){return r.forEachNamedChild(e,t)},tick:function(){return!0},afterRender:function(){},onRemove:function(){},scrollIntoView:function(e){for(var t=e;t.parent;){if(void 0!==t._parentScrollArea)return void t._parentScrollArea.focus(e,200);t=t.parent}}})})); define("CatLab/Easelbone/Views/LoadingView",["CatLab/Easelbone/Views/Base"],(function(t){"use strict";var e=new Image;e.crossOrigin="anonymous",e.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAWCAYAAAA1vze2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH3wMRCycoY5y7DgAAAeBJREFUSMel1j9sTlEYBvD3fv2DIqmmmmjTBSXVkYSBhAixWNRmMYvBgMZQSUeLQWIw2hqLWIRBYpCIQYcyYDBJNAQpBk21P8u5cdzcfv1835Pc3JznOed9zn3f95zciCbAAbzEIk5VtI04jxmMRzvAKD76i1VszvSZTFvESDsmk1jxLyYz/UtFu7pWrEYTn9EafUcyGIiIgYo23o5Jdw23kt79NVrR1AQNbEEe+F0WtMSLiIiiKN5HxGqdluL1oB89JdGFabzGLWxKfB9ms5zfqdTsYqbNoT+LN5tqdq+cfLrSQUcrOxrGCLoqJg1sxxg2ZPyZSsNcCTyvdMlUdABMpc2W+NyIiP2VecMtBjuMEzVSX2U82B0RSxHRk5H5gTsbEbcjYig1wpuI2BoRx7I5cxFxqCiK5URtq3SawINKui6nxfvwU2u4npneqGjzkQr3LRFPMZil40eLJnfLxsAQHuErHpfx1sr5Xnxq0WS6Wf2anfhzKb/r4XeqVcvt14sL//EFJZ6Uh3g9gzF80D7u4yQm1rzU8DYi9kRnWE333WBRFN/rarIQnaMREb/SU5uu3VjWOS6haFaX41iqWbjQosE8eltpgAk8TKf9FXYl/kgawzMcTPxOXMPN/B8gxx+XbHMxap3gAwAAAABJRU5ErkJggg==";var i=0,s={x:{start:0,end:100},y:{start:0,end:100}},a={x:s.x.end/2,y:s.y.end/2},n=[],h=!1;return t.extend({initialize:function(t){this.gameVersion=null,void 0!==t.gameVersion&&(this.gameVersion=t.gameVersion),this.dRotation=0,this.speed=10,this.angle=Math.random()*Math.PI*2,this.targetAngle=null,this.flippedX=!1,this.flippedY=!1,this.rotationSpeedRad=.2,this.screenMargin=50},render:function(){s={x:{start:0,end:this.el.stage.canvas.width},y:{start:0,end:this.el.stage.canvas.height}},this.loadingText=new createjs.Text("Loading","15px monospace","#ffffff"),this.loadingText.textAlign="center",this.loadingText.lineHeight=20,this.el.addChild(this.loadingText),this.versionText=null,this.gameVersion&&(this.versionText=new createjs.Text("v"+this.gameVersion,"12px monospace","#ffffff"),this.el.addChild(this.versionText),this.versionText.cache(-100,-100,200,200),this.loadingText.cache(-100,-100,200,200)),this.el.setBounds(0,0,this.el.stage.canvas.width,this.el.stage.canvas.height),this.updatePositions(),a={x:Math.random()*(s.x.end-2*this.screenMargin)+this.screenMargin,y:Math.random()*(s.y.end-2*this.screenMargin)+this.screenMargin}},tick:function(){this.updatePositions();var t=this.angle;null!==this.targetAngle&&(t=this.targetAngle),(a.xs.x.end-this.screenMargin&&Math.cos(t)>0)&&(this.targetAngle=Math.PI-t),(a.ys.y.end-this.screenMargin&&Math.sin(t)>0)&&(this.targetAngle=2*Math.PI-t),null!==this.targetAngle&&(this.targetAngle-this.angle>this.rotationSpeedRad?this.angle+=this.rotationSpeedRad:this.targetAngle-this.angle<0-this.rotationSpeedRad?this.angle-=this.rotationSpeedRad:this.targetAngle=null);var e=Math.cos(this.angle)*this.speed,h=Math.sin(this.angle)*this.speed;a.x+=e,a.y+=h,++i%6==0&&this.addPaw();for(var r=0;r0&&g.push(n[r]);return n=g,!0},updatePositions:function(){this.loadingText.x=this.el.getBounds().width/2,this.loadingText.y=this.el.getBounds().height/2,this.versionText&&(this.versionText.x=this.el.getBounds().width-60,this.versionText.y=this.el.getBounds().height-20)},stop:function(){},start:function(){},addPaw:function(){var t=10;(h=!h)&&(t*=-1);var i=new createjs.Bitmap(e),s=this.angle;i.regX=10,i.regY=10,i.x=a.x+Math.cos(s+Math.PI/2)*t,i.y=a.y+Math.sin(s+Math.PI/2)*t,i.scaleX=20/e.width,i.scaleY=20/e.height,i.rotation=(s+Math.PI/2)*(180/Math.PI),this.el.addChild(i),n.push(i)},setProgress:function(t){t=Math.floor(100*t),this.loadingText.text="Loading\n"+t+"%",this.loadingText.cache(-100,-100,200,200)}})})); define("CatLab/Easelbone/Views/Layer",["easeljs","underscore","backbone","CatLab/Easelbone/Utilities/Deferred"],(function(e,i,t,n){var r=function(n){i.extend(this,t.Events),void 0===n&&(n={}),void 0!==n.container?this.container=n.container:this.container=new e.Container,this.view=null};return r.prototype.setView=function(i){var t=this.view;this.view=i;var n=new e.Container;this.view.setElement(n),this.container.addChild(n),this.view.on("all",function(e){this.trigger("view:"+e)}.bind(this)),this.view.trigger("stage:added"),t&&this.container.children.length>0&&this.container.setChildIndex(t.el,this.container.children.length-1),t&&this._destroyView(t).then((function(){t=null}))},r.prototype.clearView=function(){this.view&&this._destroyView(this.view).pipe(function(){this.view=null}.bind(this))},r.prototype._destroyView=function(e){return this._waitForViewDestroy(e).pipe(function(){null!==e&&(e.trigger("stage:removed"),this.container.removeChild(e.el),e=null)}.bind(this))},r.prototype._waitForViewDestroy=function(e){var i=new n;return e&&void 0!==e.hasLabeledFrame&&void 0!==e.easelScreen&&e.easelScreen&&e.hasLabeledFrame("view:destroy")?(e.jumpToFrame("view:destroy").then((function(){i.resolve()})),i.promise()):(i.resolve(),i.promise())},r.prototype.render=function(){this.view&&(this.view.el.removeAllChildren(),this.view.trigger("render:before"),this.view.render(),this.view.trigger("render"),this.view.trigger("render:after"))},r.prototype.tick=function(e){return!!this.view&&this.view.tick(e)},r})); -define("CatLab/Easelbone/EaselJS/Pinner",["easeljs","CatLab/Easelbone/Utilities/DirtyFlag"],(function(n,e){"use strict";var r={_props:{},setContainer:function(n,e){n._pinContainer=e},_deferPin:function(e,i){e.stage?r.pin(e):i>=300||n.Ticker.on("tick",(function(){r._deferPin(e,i+1)}),null,!0)},pin:function(i){var t=i.stage;if(t){if(!r._isPinned(t,i)){var a=i.parent;if(a){!function(n){n._pinnedElements||(n._pinnedElements=[]),n._pinnedElements}(t),r._removeByAnchor(t,a);var o=new n.Container,s={obj:i,anchor:a,wrapper:o};(function(e){if(e._pinContainer&&e._pinContainer.stage===e)return e._pinContainer;var r=new n.Container;return e.addChild(r),e._pinContainer=r,r})(t).addChild(o),o.addChild(i),t._pinnedElements.push(s),r._syncRecord(t,s),e.invalidate()}}}else r._deferPin(i,0)},unpin:function(n){var i=n.stage,t=i&&i._pinnedElements;if(t)for(var a=0;a=0;i--){var t=e[i];t.anchor.stage===n?r._syncRecord(n,t):(r._restore(t),e.splice(i,1))}return e.length>0},_removeByAnchor:function(n,e){for(var i=n._pinnedElements,t=i.length-1;t>=0;t--)i[t].anchor===e&&(r._restore(i[t]),i.splice(t,1))},_restore:function(n){n.obj.parent&&n.obj.parent.removeChild(n.obj),n.wrapper.parent&&n.wrapper.parent.removeChild(n.wrapper),n.anchor&&n.anchor.stage&&n.anchor.addChild(n.obj)},_syncRecord:function(n,e){var i=e.wrapper;i.visible=!n._pinsHidden;var t=i.parent;if(t){var a=e.anchor.getBounds();a&&i.setBounds(0,0,a.width,a.height);var o=t.getConcatenatedMatrix().invert().appendMatrix(e.anchor.getConcatenatedMatrix()).decompose(r._props);i.x=o.x,i.y=o.y,i.scaleX=o.scaleX,i.scaleY=o.scaleY,i.rotation=o.rotation,i.skewX=o.skewX,i.skewY=o.skewY}}};return r})); +define("CatLab/Easelbone/EaselJS/Pinner",["easeljs","CatLab/Easelbone/Utilities/DirtyFlag"],(function(n,e){"use strict";var r={_props:{},setContainer:function(n,e){n._pinContainer=e},_deferPin:function(e,t){e.stage?r.pin(e):t>=300||n.Ticker.on("tick",(function(){r._deferPin(e,t+1)}),null,!0)},pin:function(t){var i=t.stage;if(i){if(!r._isPinned(i,t)){var a=t.parent;if(a){!function(n){n._pinnedElements||(n._pinnedElements=[]),n._pinnedElements}(i),r._removeByAnchor(i,a);var o=new n.Container,s={obj:t,anchor:a,wrapper:o};(function(e){if(e._pinContainer&&e._pinContainer.stage===e)return e._pinContainer;var r=new n.Container;return e.addChild(r),e._pinContainer=r,r})(i).addChild(o),o.addChild(t),t._pinnedToTop=!0,i._pinnedElements.push(s),r._syncRecord(i,s),e.invalidate()}}}else r._deferPin(t,0)},unpin:function(n){var t=n.stage,i=t&&t._pinnedElements;if(i)for(var a=0;a=0;t--){var i=e[t];i.anchor.stage===n?r._syncRecord(n,i):(r._restore(i),e.splice(t,1))}return e.length>0},_removeByAnchor:function(n,e){for(var t=n._pinnedElements,i=t.length-1;i>=0;i--)t[i].anchor===e&&(r._restore(t[i]),t.splice(i,1))},_restore:function(n){n.obj._pinnedToTop=!1,n.obj.parent&&n.obj.parent.removeChild(n.obj),n.wrapper.parent&&n.wrapper.parent.removeChild(n.wrapper),n.anchor&&n.anchor.stage&&n.anchor.addChild(n.obj)},_syncRecord:function(n,e){var t=e.wrapper;t.visible=!n._pinsHidden;var i=t.parent;if(i){var a,o;try{a=e.anchor.getConcatenatedMatrix(),o=i.getConcatenatedMatrix()}catch(n){return}if(a&&o){var s=null;try{s=e.anchor.getBounds()}catch(n){s=null}!s||e._bw===s.width&&e._bh===s.height||(e._bw=s.width,e._bh=s.height,t.setBounds(0,0,s.width,s.height));var c=o.invert().appendMatrix(a),d=e._m||(e._m={a:0,b:0,c:0,d:0,tx:0,ty:0});if(d.a!==c.a||d.b!==c.b||d.c!==c.c||d.d!==c.d||d.tx!==c.tx||d.ty!==c.ty){d.a=c.a,d.b=c.b,d.c=c.c,d.d=c.d,d.tx=c.tx,d.ty=c.ty;var p=c.decompose(r._props);t.x=p.x,t.y=p.y,t.scaleX=p.scaleX,t.scaleY=p.scaleY,t.rotation=p.rotation,t.skewX=p.skewX,t.skewY=p.skewY}}}}};return r})); define("CatLab/Easelbone/Views/Root",["backbone","easeljs","CatLab/Easelbone/Views/Layer","CatLab/Easelbone/Utilities/DirtyFlag","CatLab/Easelbone/EaselJS/Pinner"],(function(i,t,e,n,a){var s;return i.View.extend({stage:null,container:null,hudcontainer:null,view:null,hud:null,maxCanvasSize:null,width:null,height:null,initialize:function(i){this.initializeRootView(i)},initializeRootView:function(i){if(void 0!==i.canvas)this.canvas=i.canvas,this.container=this.canvas.parentNode;else{if(void 0===i.container)throw new Error("Container must be defined for root view.");this.canvas=document.createElement("canvas"),this.container=i.container,this.container.appendChild(this.canvas)}if(void 0!==i.width&&void 0!==i.height&&(this.width=i.width,this.height=i.height),this.stage=this.createStage(i),this.layers=[],this.layerMap={},this.dirty=!1,this.mainLayer=this.nextLayer("main"),this.snapToPixel=void 0!==i.snapToPixel&&i.snapToPixel,this.dirtyRendering=void 0!==i.dirtyRendering&&!!i.dirtyRendering,this.heartbeatInterval=1e3,this._lastPaintTime=0,this.dirtyRendering){this.stage.tickOnUpdate=!1,n.installMovieClipHook(t);var e=this.invalidate.bind(this);this.stage.on("stagemousedown",e),this.stage.on("stagemouseup",e),this.stage.on("stagemousemove",e)}t.Ticker.addEventListener("tick",function(i){t.Ticker.paused||this.tick(i)}.bind(this)),this.resize()},createStage:function(i){var e;return(e=void 0!==i.webgl&&i.webgl?new t.StageGL(this.canvas):new t.Stage(this.canvas)).snapToPixelEnabled=this.snapToPixel,e},setMaxCanvasSize:function(i,t){this.maxCanvasSize=[i,t]},nextLayer:function(i){void 0===i&&(i="Layer"+this.layers.length);var t=new e;return t.container.snapToPixel=this.snapToPixel,this.stage.addChild(t.container),this.layers.push(t),this.layerMap[i]=t,t},getLayer:function(i){return this.layerMap[i]},setPinContainer:function(i){a.setContainer(this.stage,i)},setView:function(i){this.mainLayer.setView(i),this.render()},invalidate:function(){n.invalidate()},tick:function(i){for(this.trigger("tick:before",i),this.dirty=!1,s=0;s=this.heartbeatInterval&&(this.dirty=!0),this.dirty&&(this._lastPaintTime=i.time,this.update())):this.dirty&&this.update(),this.trigger("tick:after")},render:function(){for(s=0;s + +Pinner + Placeholder z-index repro + + + + + + diff --git a/tools/pin-placeholder-test.js b/tools/pin-placeholder-test.js new file mode 100644 index 0000000..d53f058 --- /dev/null +++ b/tools/pin-placeholder-test.js @@ -0,0 +1,44 @@ +// Verifies a pinned Placeholder stays in the pin layer despite +// Placeholder.updateZIndex re-inserting inner placeholders each tick. +// Serves the repo root itself. Usage: node tools/pin-placeholder-test.js [port] +var spawn = require('child_process').spawn; +var chromium = require('playwright').chromium; + +var FIXTURE = 'tools/fixtures/pin-placeholder.html'; +var PORT = parseInt(process.argv[2], 10) || 8171; + +function isGreen(rgb) { return rgb[1] > 150 && rgb[0] < 100 && rgb[2] < 100; } + +async function main() { + var server = spawn('node', ['node_modules/http-server/bin/http-server', '.', '-p', String(PORT), '-c-1'], { stdio: 'ignore' }); + await new Promise(function (r) { setTimeout(r, 1500); }); + var browser = await chromium.launch(); + var failures = []; + try { + var page = await browser.newPage(); + var errors = []; + page.on('pageerror', function (e) { errors.push(e.message); }); + page.on('console', function (m) { if (m.type() === 'error') errors.push(m.text()); }); + await page.goto('http://localhost:' + PORT + '/' + FIXTURE, { waitUntil: 'load', timeout: 30000 }); + await page.waitForFunction('window.__ready === true', { timeout: 10000 }); + + var initial = await page.evaluate('window.__centerRGB()'); + if (!isGreen(initial)) { failures.push('expected pinned placeholder GREEN at (125,125), got ' + initial); } + + // Run several ticks so Placeholder.updateZIndex fires repeatedly. + await page.evaluate('window.__tick()'); + var afterTicks = await page.evaluate('window.__centerRGB()'); + var inPinLayer = await page.evaluate('window.__placeholderInPinLayer()'); + if (!isGreen(afterTicks)) { failures.push('after ticks: expected GREEN to stay on top at (125,125), got ' + afterTicks + ' (updateZIndex dragged the placeholder back below the occluder)'); } + if (!inPinLayer) { failures.push('after ticks: expected pinned placeholder to remain in the pin layer'); } + + if (errors.length) { failures.push('page errors: ' + errors.join('; ')); } + await page.close(); + } finally { + await browser.close(); + server.kill(); + } + if (failures.length) { console.error('FAIL\n' + failures.join('\n')); process.exit(1); } + console.log('PASS'); +} +main(); diff --git a/tools/pin-test.js b/tools/pin-test.js index 4d6655f..b1ecde5 100644 --- a/tools/pin-test.js +++ b/tools/pin-test.js @@ -112,6 +112,13 @@ async function main() { var pinnedAfterAttach = await page.evaluate('window.__deferBoxPinned()'); if (!pinnedAfterAttach) { failures.push('deferred pin: box should be pinned after its anchor is attached to the stage'); } + // Crash guard: an anchor whose getBounds() throws on first access must + // not break the tick loop; the pin still places once it stops throwing. + await page.evaluate('window.__pinThrowingAnchor()'); + await page.waitForTimeout(100); + var throwSpot = await pixel(page, 315, 315); + if (!isGreen(throwSpot)) { failures.push('throwing-anchor: expected GREEN at (315,315), got ' + throwSpot); } + if (errors.length) { failures.push('page errors: ' + errors.join('; ')); } await page.close(); } finally {