summaryrefslogtreecommitdiff
path: root/js/dojo-1.7.2/dojox/gfx/canvasWithEvents.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/dojo-1.7.2/dojox/gfx/canvasWithEvents.js')
-rw-r--r--js/dojo-1.7.2/dojox/gfx/canvasWithEvents.js643
1 files changed, 643 insertions, 0 deletions
diff --git a/js/dojo-1.7.2/dojox/gfx/canvasWithEvents.js b/js/dojo-1.7.2/dojox/gfx/canvasWithEvents.js
new file mode 100644
index 0000000..169928b
--- /dev/null
+++ b/js/dojo-1.7.2/dojox/gfx/canvasWithEvents.js
@@ -0,0 +1,643 @@
+//>>built
+define("dojox/gfx/canvasWithEvents", ["dojo/_base/lang", "dojo/_base/declare", "dojo/_base/connect", "dojo/_base/Color", "dojo/dom",
+ "dojo/dom-geometry", "./_base","./canvas", "./shape", "./matrix"],
+ function(lang, declare, hub, Color, dom, domGeom, g, canvas, shapeLib, m){
+/*=====
+ dojox.gfx.canvasWithEvents = {
+ // module:
+ // dojox/gfx/canvasWithEvents
+ // summary:
+ // This the graphics rendering bridge for W3C Canvas compliant browsers which extends
+ // the basic canvas drawing renderer bridge to add additional support for graphics events
+ // on Shapes.
+ // Since Canvas is an immediate mode graphics api, with no object graph or
+ // eventing capabilities, use of the canvas module alone will only add in drawing support.
+ // This additional module, canvasWithEvents extends this module with additional support
+ // for handling events on Canvas. By default, the support for events is now included
+ // however, if only drawing capabilities are needed, canvas event module can be disabled
+ // using the dojoConfig option, canvasEvents:true|false.
+ // The id of the Canvas renderer is 'canvasWithEvents'. This id can be used when switch Dojo's
+ // graphics context between renderer implementations. See dojox.gfx._base switchRenderer
+ // API.
+ };
+ g = dojox.gfx;
+ canvas.Shape = dojox.gfx.canvas.Shape;
+ canvas.Group = dojox.gfx.canvas.Group;
+ canvas.Image = dojox.gfx.canvas.Image;
+ canvas.Text = dojox.gfx.canvas.Text;
+ canvas.Rect = dojox.gfx.canvas.Rect;
+ canvas.Circle = dojox.gfx.canvas.Circle;
+ canvas.Ellipse = dojox.gfx.canvas.Ellipse;
+ canvas.Line = dojox.gfx.canvas.Line;
+ canvas.PolyLine = dojox.gfx.canvas.PolyLine;
+ canvas.TextPath = dojox.gfx.canvas.TextPath;
+ canvas.Path = dojox.gfx.canvas.Path;
+ canvas.Surface = dojox.gfx.canvas.Surface;
+ canvasEvent.Shape = dojox.gfx.canvasWithEvents.Shape;
+
+ =====*/
+ var canvasEvent = g.canvasWithEvents = {};
+
+ declare("dojox.gfx.canvasWithEvents.Shape", canvas.Shape, {
+
+ _testInputs: function(/* Object */ctx, /* Array */ pos){
+ if (!this.canvasFill && this.strokeStyle) {
+ // pixel-based until a getStrokedPath-like api is available on the path
+ this._hitTestPixel(ctx, pos);
+ } else {
+ this._renderShape(ctx);
+ var cnt = pos.length, t = this.getTransform();
+ for (var i = 0; i < pos.length; ++i) {
+ var input = pos[i];
+ // already hit
+ if (input.target)
+ continue;
+ var x = input.x, y = input.y;
+ var p = t ? m.multiplyPoint(m.invert(t), x, y) : {
+ x: x,
+ y: y
+ };
+ input.target = this._hitTestGeometry(ctx, p.x, p.y);
+ }
+ }
+ },
+ _hitTestPixel: function(/* Object */ctx, /* Array */ pos){
+ for (var i = 0; i < pos.length; ++i) {
+ var input = pos[i];
+ if (input.target)
+ continue;
+ var x = input.x, y = input.y;
+ ctx.clearRect(0,0,1,1);
+ ctx.save();
+ ctx.translate(-x, -y);
+ this._render(ctx, true);
+ input.target = ctx.getImageData(0, 0, 1, 1).data[0] ? this : null;
+ ctx.restore();
+ }
+ },
+ _hitTestGeometry: function(ctx, x, y){
+ return ctx.isPointInPath(x, y) ? this : null;
+ },
+
+ _renderFill: function(/* Object */ ctx, /* Boolean */ apply){
+ // summary:
+ // render fill for the shape
+ // ctx:
+ // a canvas context object
+ // apply:
+ // whether ctx.fill() shall be called
+ if(ctx.pickingMode){
+ if("canvasFill" in this && apply){ ctx.fill(); }
+ return;
+ }
+ this.inherited(arguments);
+ },
+ _renderStroke: function(/* Object */ ctx, /* Boolean */ apply){
+ // summary:
+ // render stroke for the shape
+ // ctx:
+ // a canvas context object
+ // apply:
+ // whether ctx.stroke() shall be called
+ if (this.strokeStyle && ctx.pickingMode) {
+ var c = this.strokeStyle.color;
+ try {
+ this.strokeStyle.color = new Color(ctx.strokeStyle);
+ this.inherited(arguments);
+ } finally {
+ this.strokeStyle.color = c;
+ }
+ } else{
+ this.inherited(arguments);
+ }
+ },
+
+ // events
+
+ getEventSource: function(){
+ // summary: returns this gfx shape event source, which is the surface rawnode in the case of canvas.
+
+ return this.surface.getEventSource();
+ },
+
+ connect: function(name, object, method){
+ // summary: connects a handler to an event on this shape
+ this.surface._setupEvents(name); // setup events on demand
+ // No need to fix callback. The listeners registered by
+ // '_setupEvents()' are always invoked first and they
+ // already 'fix' the event
+ return arguments.length > 2 ? // Object
+ hub.connect(this, name, object, method) : hub.connect(this, name, object);
+ },
+ disconnect: function(token){
+ // summary: disconnects an event handler
+ hub.disconnect(token);
+ },
+ // connect hook
+ oncontextmenu: function(){},
+ onclick: function(){},
+ ondblclick: function(){},
+ onmouseenter: function(){},
+ onmouseleave: function(){},
+ onmouseout: function(){},
+ onmousedown: function(){},
+ ontouchstart: function(){},
+ touchstart: function(){},
+ onmouseup: function(){},
+ ontouchend: function(){},
+ touchend: function(){},
+ onmouseover: function(){},
+ onmousemove: function(){},
+ ontouchmove: function(){},
+ touchmove: function(){},
+ onkeydown: function(){},
+ onkeyup: function(){}
+ });
+
+ declare("dojox.gfx.canvasWithEvents.Group", [canvasEvent.Shape, canvas.Group], {
+ _testInputs: function(/*Object*/ctx, /*Array*/ pos){
+ var children = this.children, t = this.getTransform(), i, j;
+ if(children.length == 0){
+ return;
+ }
+ var posbk = [];
+ for(i = 0; i < pos.length; ++i){
+ var input = pos[i];
+ // backup position before transform applied
+ posbk[i] = {
+ x: input.x,
+ y: input.y
+ };
+ if(input.target) continue;
+ var x = input.x, y = input.y;
+ var p = t ? m.multiplyPoint(m.invert(t), x, y) : {
+ x: x,
+ y: y
+ };
+ input.x = p.x;
+ input.y = p.y;
+ }
+ for(i = children.length - 1; i >= 0; --i){
+ children[i]._testInputs(ctx, pos);
+ // does it need more hit tests ?
+ var allFound = true;
+ for(j = 0; j < pos.length; ++j){
+ if(pos[j].target == null){
+ allFound = false;
+ break;
+ }
+ }
+ if(allFound){
+ break;
+ }
+ }
+ for(i = 0; i < pos.length; ++i){
+ pos[i].x = posbk[i].x;
+ pos[i].y = posbk[i].y;
+ }
+ }
+ });
+
+ declare("dojox.gfx.canvasWithEvents.Image", [canvasEvent.Shape, canvas.Image], {
+ _renderShape: function(/* Object */ ctx){
+ // summary:
+ // render image
+ // ctx:
+ // a canvas context object
+ var s = this.shape;
+ if(ctx.pickingMode){
+ ctx.fillRect(s.x, s.y, s.width, s.height);
+ }else{
+ this.inherited(arguments);
+ }
+ },
+
+ _hitTestGeometry: function(ctx, x, y){
+ // TODO: improve hit testing to take into account transparency
+ var s = this.shape;
+ return x >= s.x && x <= s.x + s.width && y >= s.y && y <= s.y + s.height ? this : null;
+ }
+ });
+
+ declare("dojox.gfx.canvasWithEvents.Text", [canvasEvent.Shape, canvas.Text], {
+ _testInputs: function(ctx, pos){
+ return this._hitTestPixel(ctx, pos);
+ }
+ });
+
+
+ declare("dojox.gfx.canvasWithEvents.Rect", [canvasEvent.Shape, canvas.Rect], {});
+ declare("dojox.gfx.canvasWithEvents.Circle", [canvasEvent.Shape, canvas.Circle], {});
+ declare("dojox.gfx.canvasWithEvents.Ellipse", [canvasEvent.Shape, canvas.Ellipse],{});
+ declare("dojox.gfx.canvasWithEvents.Line", [canvasEvent.Shape, canvas.Line],{});
+ declare("dojox.gfx.canvasWithEvents.Polyline", [canvasEvent.Shape, canvas.Polyline],{});
+ declare("dojox.gfx.canvasWithEvents.Path", [canvasEvent.Shape, canvas.Path],{});
+ declare("dojox.gfx.canvasWithEvents.TextPath", [canvasEvent.Shape, canvas.TextPath],{});
+
+
+ // a map that redirects shape-specific events to the canvas event handler that deals with these events
+ var _eventsRedirectMap = {
+ onmouseenter : 'onmousemove',
+ onmouseleave : 'onmousemove',
+ onmouseout : 'onmousemove',
+ onmouseover : 'onmousemove',
+ touchstart : 'ontouchstart',
+ touchend : 'ontouchend',
+ touchmove : 'ontouchmove'
+ };
+ var _eventsShortNameMap = {
+ ontouchstart : 'touchstart',
+ ontouchend : 'touchend',
+ ontouchmove : 'touchmove'
+ };
+
+ var uagent = navigator.userAgent.toLowerCase(),
+ isiOS = uagent.search('iphone') > -1 ||
+ uagent.search('ipad') > -1 ||
+ uagent.search('ipod') > -1;
+
+ declare("dojox.gfx.canvasWithEvents.Surface", canvas.Surface, {
+ constructor:function(){
+ this._pick = { curr: null, last: null };
+ this._pickOfMouseDown = null;
+ this._pickOfMouseUp = null;
+ },
+
+ connect: function(/*String*/name, /*Object*/object, /*Function|String*/method){
+ // summary: connects a handler to an event on this surface
+ // name : String
+ // The event name
+ // object: Object
+ // The object that method will receive as "this".
+ // method: Function
+ // A function reference, or name of a function in context.
+
+ if (name.indexOf('touch') !== -1) {
+ // in case of surface.connect('touchXXX'...), we must root the handler to the
+ // specific touch event processing (done in fireTouchEvents) so that the event is properly configured.
+ // So, we activate the shape-level event processing calling _setupEvents,
+ // and connect to the _ontouchXXXImpl_ hooks that are called back by invokeHandler()
+ this._setupEvents(name);
+ name = "_on" + name + "Impl_";
+ return hub.connect(this, name, object, method);
+ } else {
+ this._initMirrorCanvas();
+ return hub.connect(this.getEventSource(), name, null,
+ shapeLib.fixCallback(this, g.fixTarget, object, method));
+ }
+ },
+
+ // connection hooks for touch events connect
+ _ontouchstartImpl_: function(){},
+ _ontouchendImpl_: function(){},
+ _ontouchmoveImpl_: function(){},
+
+ _initMirrorCanvas: function(){
+ if (!this.mirrorCanvas) {
+ var p = this._parent, mirror = p.ownerDocument.createElement("canvas");
+ mirror.width = 1;
+ mirror.height = 1;
+ mirror.style.position = 'absolute';
+ mirror.style.left = '-99999px';
+ mirror.style.top = '-99999px';
+ p.appendChild(mirror);
+ this.mirrorCanvas = mirror;
+ }
+ },
+
+ _setupEvents: function(eventName){
+ // summary:
+ // setup event listeners if not yet
+
+ // onmouseenter and onmouseleave shape events are handled in the onmousemove surface handler
+ if (eventName in _eventsRedirectMap)
+ eventName = _eventsRedirectMap[eventName];
+ if (this._eventsH && this._eventsH[eventName]) {
+ // the required listener has already been connected
+ return;
+ }
+ // a mirror canvas for shape picking
+ this._initMirrorCanvas();
+ if (!this._eventsH)
+ this._eventsH = {};
+ // register event hooks if not done yet
+ this._eventsH[eventName] = hub.connect(this.getEventSource(), eventName,
+ shapeLib.fixCallback(this, g.fixTarget, this, "_" + eventName));
+ if (eventName === 'onclick' || eventName==='ondblclick') {
+ if(!this._eventsH['onmousedown']){
+ this._eventsH['onmousedown'] = hub.connect(this.getEventSource(),
+ 'onmousedown', shapeLib.fixCallback(this, g.fixTarget, this, "_onmousedown"));
+ }
+ if(!this._eventsH['onmouseup']){
+ this._eventsH['onmouseup'] = hub.connect(this.getEventSource(),
+ 'onmouseup', shapeLib.fixCallback(this, g.fixTarget, this, "_onmouseup"));
+ }
+ }
+ },
+
+ destroy: function(){
+ // summary: stops the move, deletes all references, so the object can be garbage-collected
+ canvas.Surface.destroy.apply(this);
+
+ // destroy events and objects
+ for(var i in this._eventsH){
+ hub.disconnect(this._eventsH[i]);
+ }
+ this._eventsH = this.mirrorCanvas = null;
+ },
+
+ // events
+ getEventSource: function(){
+ // summary: returns the canvas DOM node for surface-level events
+ return this.rawNode;
+ },
+
+ // internal handlers used to implement shape-level event notification
+ _invokeHandler: function(base, method, event){
+ // Invokes handler function
+ var handler = base[method];
+ if(handler && handler.after){
+ handler.apply(base, [event]);
+ }else if (method in _eventsShortNameMap){
+ // there may be a synonym event name (touchstart -> ontouchstart)
+ handler = base[_eventsShortNameMap[method]];
+ if(handler && handler.after){
+ handler.apply(base, [event]);
+ }
+ }
+ if(!handler && method.indexOf('touch') !== -1){
+ // special case for surface touch handlers
+ method = "_" + method + "Impl_";
+ handler = base[method];
+ if(handler){
+ handler.apply(base, [event]);
+ }
+ }
+ // Propagates event up in the DOM hierarchy only if event
+ // has not been stopped (event.cancelBubble is true)
+ if (!isEventStopped(event) && base.parent) {
+ this._invokeHandler(base.parent, method, event);
+ }
+ },
+ _oncontextmenu: function(e){
+ // summary: triggers onclick
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if(this._pick.curr){
+ this._invokeHandler(this._pick.curr, 'oncontextmenu', e);
+ }
+ },
+ _ondblclick: function(e){
+ // summary: triggers onclick
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if(this._pickOfMouseUp){
+ this._invokeHandler(this._pickOfMouseUp, 'ondblclick', e);
+ }
+ },
+ _onclick: function(e){
+ // summary: triggers onclick
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if(this._pickOfMouseUp && this._pickOfMouseUp == this._pickOfMouseDown){
+ this._invokeHandler(this._pickOfMouseUp, 'onclick', e);
+ }
+ },
+ _onmousedown: function(e){
+ // summary: triggers onmousedown
+ this._pickOfMouseDown = this._pick.curr;
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if(this._pick.curr){
+ this._invokeHandler(this._pick.curr, 'onmousedown', e);
+ }
+ },
+ _ontouchstart: function(e){
+ // summary: triggers ontouchstart
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if (this._pick.curr) {
+ this._fireTouchEvent(e);
+ }
+
+ },
+ _onmouseup: function(e){
+ // summary: triggers onmouseup
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ this._pickOfMouseUp = this._pick.curr;
+ if(this._pick.curr){
+ this._invokeHandler(this._pick.curr, 'onmouseup', e);
+ }
+ },
+ _ontouchend: function(e){
+ // summary: triggers ontouchend
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if(this._pick.curr){
+ for(var i = 0; i < this._pick.curr.length; ++i){
+ if(this._pick.curr[i].target){
+ e.gfxTarget = this._pick.curr[i].target;
+ this._invokeHandler(this._pick.curr[i].target, 'ontouchend', e);
+ }
+ }
+ }
+ },
+ _onmousemove: function(e){
+ // summary: triggers onmousemove, onmouseenter, onmouseleave
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ if(this._pick.last && this._pick.last != this._pick.curr){
+ this._invokeHandler(this._pick.last, 'onmouseleave', e);
+ this._invokeHandler(this._pick.last, 'onmouseout', e);
+ }
+ if(this._pick.curr){
+ if(this._pick.last == this._pick.curr){
+ this._invokeHandler(this._pick.curr, 'onmousemove', e);
+ }else{
+ this._invokeHandler(this._pick.curr, 'onmouseenter', e);
+ this._invokeHandler(this._pick.curr, 'onmouseover', e);
+ }
+ }
+ },
+ _ontouchmove: function(e){
+ // summary: triggers ontouchmove
+ if(this._pick.curr){
+ this._fireTouchEvent(e);
+ }
+ },
+
+ _fireTouchEvent: function(e){
+ // this._pick.curr = an array of target for touch event, one target instance for mouse events
+ var toFire = []; // the per-shape events list to fire
+ // for each positive picking:
+ // .group all pickings by target
+ // .collect all touches for the picking target
+ for(var i = 0; i < this._pick.curr.length; ++i){
+ var pick = this._pick.curr[i];
+ if(pick.target){
+ // touches for this target
+ var gfxtt = pick.target.__gfxtt;
+ if(!gfxtt){
+ gfxtt = [];
+ pick.target.__gfxtt = gfxtt;
+ }
+ // store the touch that yielded to this picking
+ gfxtt.push(pick.t);
+ // if the target has not been added yet, add it
+ if(!pick.target.__inToFire){
+ toFire.push(pick.target);
+ pick.target.__inToFire=true;
+ }
+ }
+ }
+ if(toFire.length === 0){
+ // no target, invokes the surface handler
+ this._invokeHandler(this, 'on' + e.type, e);
+ }else{
+ for(i = 0; i < toFire.length; ++i){
+ (function(){
+ var targetTouches = toFire[i].__gfxtt;
+ // fires the original event BUT with our own targetTouches array.
+ // Note for iOS:
+ var evt = lang.delegate(e, {gfxTarget: toFire[i]});
+ if(isiOS){
+ // must use the original preventDefault function or iOS will throw a TypeError
+ evt.preventDefault = function(){e.preventDefault();};
+ evt.stopPropagation = function(){e.stopPropagation();};
+ }
+ // override targetTouches with the filtered one
+ evt.__defineGetter__('targetTouches', function(){return targetTouches;});
+ // clean up
+ delete toFire[i].__gfxtt;
+ delete toFire[i].__inToFire;
+ // fire event
+ this._invokeHandler(toFire[i], 'on' + e.type, evt);
+ }).call(this);
+ }
+ }
+ },
+ _onkeydown: function(){}, // needed?
+ _onkeyup: function(){}, // needed?
+
+ _whatsUnderEvent: function(evt){
+ // summary: returns the shape under the mouse event
+ // evt: mouse event
+
+ var surface = this, i,
+ pos = domGeom.position(surface.rawNode, true),
+ inputs = [], changedTouches = evt.changedTouches, touches = evt.touches;
+ // collect input events targets
+ if(changedTouches){
+ for(i = 0; i < changedTouches.length; ++i){
+ inputs.push({
+ t: changedTouches[i],
+ x: changedTouches[i].pageX - pos.x,
+ y: changedTouches[i].pageY - pos.y
+ });
+ }
+ }else if(touches){
+ for(i = 0; i < touches.length; ++i){
+ inputs.push({
+ t: touches[i],
+ x: touches[i].pageX - pos.x,
+ y: touches[i].pageY - pos.y
+ });
+ }
+ }else{
+ inputs.push({
+ x : evt.pageX - pos.x,
+ y : evt.pageY - pos.y
+ });
+ }
+
+ var mirror = surface.mirrorCanvas,
+ ctx = mirror.getContext('2d'),
+ children = surface.children;
+
+ ctx.clearRect(0, 0, mirror.width, mirror.height);
+ ctx.save();
+ ctx.strokeStyle = "rgba(127,127,127,1.0)";
+ ctx.fillStyle = "rgba(127,127,127,1.0)";
+ ctx.pickingMode = true;
+ var pick = null;
+ // process the inputs to find the target.
+ for(i = children.length-1; i >= 0; i--){
+ children[i]._testInputs(ctx, inputs);
+ // does it need more hit tests ?
+ var allFound = true;
+ for(j = 0; j < inputs.length; ++j){
+ if(inputs[j].target == null){
+ allFound = false;
+ break;
+ }
+ }
+ if(allFound){
+ break;
+ }
+ }
+ ctx.restore();
+ // touch event handlers expect an array of target, mouse handlers one target
+ return (touches || changedTouches) ? inputs : inputs[0].target;
+ }
+ });
+
+ canvasEvent.createSurface = function(parentNode, width, height){
+ // summary: creates a surface (Canvas)
+ // parentNode: Node: a parent node
+ // width: String: width of surface, e.g., "100px"
+ // height: String: height of surface, e.g., "100px"
+
+ if(!width && !height){
+ var pos = domGeom.position(parentNode);
+ width = width || pos.w;
+ height = height || pos.h;
+ }
+ if(typeof width == "number"){
+ width = width + "px";
+ }
+ if(typeof height == "number"){
+ height = height + "px";
+ }
+
+ var s = new canvasEvent.Surface(),
+ p = dom.byId(parentNode),
+ c = p.ownerDocument.createElement("canvas");
+
+ c.width = g.normalizedLength(width); // in pixels
+ c.height = g.normalizedLength(height); // in pixels
+
+ p.appendChild(c);
+ s.rawNode = c;
+ s._parent = p;
+ s.surface = s;
+ return s; // dojox.gfx.Surface
+ };
+
+
+ // Mouse/Touch event
+ var isEventStopped = function(/*Event*/ evt){
+ // summary:
+ // queries whether an event has been stopped or not
+ // evt: Event
+ // The event object.
+ if(evt.cancelBubble !== undefined){
+ return evt.cancelBubble;
+ }
+ return false;
+ };
+
+ canvasEvent.fixTarget = function(event, gfxElement){
+ // summary:
+ // Adds the gfxElement to event.gfxTarget if none exists. This new
+ // property will carry the GFX element associated with this event.
+ // event: Object
+ // The current input event (MouseEvent or TouchEvent)
+ // gfxElement: Object
+ // The GFX target element (a Surface in this case)
+ if(isEventStopped(event)){
+ return false;
+ }
+ if(!event.gfxTarget){
+ gfxElement._pick.last = gfxElement._pick.curr;
+ gfxElement._pick.curr = gfxElement._whatsUnderEvent(event);
+ if (!lang.isArray(gfxElement._pick.curr))
+ event.gfxTarget = gfxElement._pick.curr;
+ }
+ return true;
+ };
+
+ return canvasEvent;
+});