123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642 |
- 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;
- });
|