webui-aria2/js/libs/dojox/gfx/canvasWithEvents.js.uncompressed.js

644 lines
20 KiB
JavaScript
Raw Normal View History

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