1 | define(["dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on", "dojo/aspect", "dojo/touch", "dojo/_base/Color", "dojo/dom", |
---|
2 | "dojo/dom-geometry", "dojo/_base/window", "./_base","./canvas", "./shape", "./matrix"], |
---|
3 | function(lang, declare, has, on, aspect, touch, Color, dom, domGeom, win, g, canvas, shapeLib, m){ |
---|
4 | function makeFakeEvent(event){ |
---|
5 | // summary: |
---|
6 | // Generates a "fake", fully mutable event object by copying the properties from an original host Event |
---|
7 | // object to a new standard JavaScript object. |
---|
8 | |
---|
9 | var fakeEvent = {}; |
---|
10 | for(var k in event){ |
---|
11 | if(typeof event[k] === "function"){ |
---|
12 | // Methods (like preventDefault) must be invoked on the original event object, or they will not work |
---|
13 | fakeEvent[k] = lang.hitch(event, k); |
---|
14 | } |
---|
15 | else{ |
---|
16 | fakeEvent[k] = event[k]; |
---|
17 | } |
---|
18 | } |
---|
19 | return fakeEvent; |
---|
20 | } |
---|
21 | |
---|
22 | // Browsers that implement the current (January 2013) WebIDL spec allow Event object properties to be mutated |
---|
23 | // using Object.defineProperty; some older WebKits (Safari 6-) and at least IE10- do not follow the spec. Direct |
---|
24 | // mutation is, of course, much faster when it can be done. |
---|
25 | has.add("dom-mutableEvents", function(){ |
---|
26 | var event = document.createEvent("UIEvents"); |
---|
27 | try { |
---|
28 | if(Object.defineProperty){ |
---|
29 | Object.defineProperty(event, "type", { value: "foo" }); |
---|
30 | }else{ |
---|
31 | event.type = "foo"; |
---|
32 | } |
---|
33 | return event.type === "foo"; |
---|
34 | }catch(e){ |
---|
35 | return false; |
---|
36 | } |
---|
37 | }); |
---|
38 | |
---|
39 | has.add("MSPointer", navigator.msPointerEnabled); |
---|
40 | has.add("pointer-events", navigator.pointerEnabled); |
---|
41 | |
---|
42 | var canvasWithEvents = g.canvasWithEvents = { |
---|
43 | // summary: |
---|
44 | // This the graphics rendering bridge for W3C Canvas compliant browsers which extends |
---|
45 | // the basic canvas drawing renderer bridge to add additional support for graphics events |
---|
46 | // on Shapes. |
---|
47 | // Since Canvas is an immediate mode graphics api, with no object graph or |
---|
48 | // eventing capabilities, use of the canvas module alone will only add in drawing support. |
---|
49 | // This additional module, canvasWithEvents extends this module with additional support |
---|
50 | // for handling events on Canvas. By default, the support for events is now included |
---|
51 | // however, if only drawing capabilities are needed, canvas event module can be disabled |
---|
52 | // using the dojoConfig option, canvasEvents:true|false. |
---|
53 | }; |
---|
54 | |
---|
55 | canvasWithEvents.Shape = declare("dojox.gfx.canvasWithEvents.Shape", canvas.Shape, { |
---|
56 | _testInputs: function(/* Object */ ctx, /* Array */ pos){ |
---|
57 | if(this.clip || (!this.canvasFill && this.strokeStyle)){ |
---|
58 | // pixel-based until a getStrokedPath-like api is available on the path |
---|
59 | this._hitTestPixel(ctx, pos); |
---|
60 | }else{ |
---|
61 | this._renderShape(ctx); |
---|
62 | var length = pos.length, |
---|
63 | t = this.getTransform(); |
---|
64 | |
---|
65 | for(var i = 0; i < length; ++i){ |
---|
66 | var input = pos[i]; |
---|
67 | // already hit |
---|
68 | if(input.target){continue;} |
---|
69 | var x = input.x, |
---|
70 | y = input.y, |
---|
71 | p = t ? m.multiplyPoint(m.invert(t), x, y) : { x: x, y: y }; |
---|
72 | input.target = this._hitTestGeometry(ctx, p.x, p.y); |
---|
73 | } |
---|
74 | } |
---|
75 | }, |
---|
76 | |
---|
77 | _hitTestPixel: function(/* Object */ ctx, /* Array */ pos){ |
---|
78 | for(var i = 0; i < pos.length; ++i){ |
---|
79 | var input = pos[i]; |
---|
80 | if(input.target){continue;} |
---|
81 | var x = input.x, |
---|
82 | y = input.y; |
---|
83 | ctx.clearRect(0,0,1,1); |
---|
84 | ctx.save(); |
---|
85 | ctx.translate(-x, -y); |
---|
86 | this._render(ctx, true); |
---|
87 | input.target = ctx.getImageData(0, 0, 1, 1).data[0] ? this : null; |
---|
88 | ctx.restore(); |
---|
89 | } |
---|
90 | }, |
---|
91 | |
---|
92 | _hitTestGeometry: function(ctx, x, y){ |
---|
93 | return ctx.isPointInPath(x, y) ? this : null; |
---|
94 | }, |
---|
95 | |
---|
96 | _renderFill: function(/* Object */ ctx, /* Boolean */ apply){ |
---|
97 | // summary: |
---|
98 | // render fill for the shape |
---|
99 | // ctx: |
---|
100 | // a canvas context object |
---|
101 | // apply: |
---|
102 | // whether ctx.fill() shall be called |
---|
103 | if(ctx.pickingMode){ |
---|
104 | if("canvasFill" in this && apply){ |
---|
105 | ctx.fill(); |
---|
106 | } |
---|
107 | return; |
---|
108 | } |
---|
109 | this.inherited(arguments); |
---|
110 | }, |
---|
111 | |
---|
112 | _renderStroke: function(/* Object */ ctx){ |
---|
113 | // summary: |
---|
114 | // render stroke for the shape |
---|
115 | // ctx: |
---|
116 | // a canvas context object |
---|
117 | // apply: |
---|
118 | // whether ctx.stroke() shall be called |
---|
119 | if(this.strokeStyle && ctx.pickingMode){ |
---|
120 | var c = this.strokeStyle.color; |
---|
121 | try{ |
---|
122 | this.strokeStyle.color = new Color(ctx.strokeStyle); |
---|
123 | this.inherited(arguments); |
---|
124 | }finally{ |
---|
125 | this.strokeStyle.color = c; |
---|
126 | } |
---|
127 | }else{ |
---|
128 | this.inherited(arguments); |
---|
129 | } |
---|
130 | }, |
---|
131 | |
---|
132 | // events |
---|
133 | |
---|
134 | getEventSource: function(){ |
---|
135 | return this.surface.rawNode; |
---|
136 | }, |
---|
137 | |
---|
138 | on: function(type, listener){ |
---|
139 | // summary: |
---|
140 | // Connects an event to this shape. |
---|
141 | |
---|
142 | var expectedTarget = this.rawNode; |
---|
143 | |
---|
144 | // note that event listeners' targets are automatically fixed up in the canvas's addEventListener method |
---|
145 | return on(this.getEventSource(), type, function(event){ |
---|
146 | if(dom.isDescendant(event.target, expectedTarget)){ |
---|
147 | listener.apply(expectedTarget, arguments); |
---|
148 | } |
---|
149 | }); |
---|
150 | }, |
---|
151 | |
---|
152 | connect: function(name, object, method){ |
---|
153 | // summary: |
---|
154 | // Deprecated. Connects a handler to an event on this shape. Use `on` instead. |
---|
155 | |
---|
156 | if(name.substring(0, 2) == "on"){ |
---|
157 | name = name.substring(2); |
---|
158 | } |
---|
159 | return this.on(name, method ? lang.hitch(object, method) : lang.hitch(null, object)); |
---|
160 | }, |
---|
161 | |
---|
162 | disconnect: function(handle){ |
---|
163 | // summary: |
---|
164 | // Deprecated. Disconnects an event handler. Use `handle.remove` instead. |
---|
165 | |
---|
166 | handle.remove(); |
---|
167 | } |
---|
168 | }); |
---|
169 | |
---|
170 | canvasWithEvents.Group = declare("dojox.gfx.canvasWithEvents.Group", [canvasWithEvents.Shape, canvas.Group], { |
---|
171 | _testInputs: function(/*Object*/ ctx, /*Array*/ pos){ |
---|
172 | var children = this.children, |
---|
173 | t = this.getTransform(), |
---|
174 | i, |
---|
175 | j, |
---|
176 | input; |
---|
177 | |
---|
178 | if(children.length === 0){ |
---|
179 | return; |
---|
180 | } |
---|
181 | var posbk = []; |
---|
182 | for(i = 0; i < pos.length; ++i){ |
---|
183 | input = pos[i]; |
---|
184 | // backup position before transform applied |
---|
185 | posbk[i] = { |
---|
186 | x: input.x, |
---|
187 | y: input.y |
---|
188 | }; |
---|
189 | if(input.target){continue;} |
---|
190 | var x = input.x, y = input.y; |
---|
191 | var p = t ? m.multiplyPoint(m.invert(t), x, y) : { x: x, y: y }; |
---|
192 | input.x = p.x; |
---|
193 | input.y = p.y; |
---|
194 | } |
---|
195 | for(i = children.length - 1; i >= 0; --i){ |
---|
196 | children[i]._testInputs(ctx, pos); |
---|
197 | // does it need more hit tests ? |
---|
198 | var allFound = true; |
---|
199 | for(j = 0; j < pos.length; ++j){ |
---|
200 | if(pos[j].target == null){ |
---|
201 | allFound = false; |
---|
202 | break; |
---|
203 | } |
---|
204 | } |
---|
205 | if(allFound){ |
---|
206 | break; |
---|
207 | } |
---|
208 | } |
---|
209 | if(this.clip){ |
---|
210 | // filter positive hittests against the group clipping area |
---|
211 | for(i = 0; i < pos.length; ++i){ |
---|
212 | input = pos[i]; |
---|
213 | input.x = posbk[i].x; |
---|
214 | input.y = posbk[i].y; |
---|
215 | if(input.target){ |
---|
216 | ctx.clearRect(0,0,1,1); |
---|
217 | ctx.save(); |
---|
218 | ctx.translate(-input.x, -input.y); |
---|
219 | this._render(ctx, true); |
---|
220 | if(!ctx.getImageData(0, 0, 1, 1).data[0]){ |
---|
221 | input.target = null; |
---|
222 | } |
---|
223 | ctx.restore(); |
---|
224 | } |
---|
225 | } |
---|
226 | }else{ |
---|
227 | for(i = 0; i < pos.length; ++i){ |
---|
228 | pos[i].x = posbk[i].x; |
---|
229 | pos[i].y = posbk[i].y; |
---|
230 | } |
---|
231 | } |
---|
232 | } |
---|
233 | |
---|
234 | }); |
---|
235 | |
---|
236 | canvasWithEvents.Image = declare("dojox.gfx.canvasWithEvents.Image", [canvasWithEvents.Shape, canvas.Image], { |
---|
237 | _renderShape: function(/* Object */ ctx){ |
---|
238 | // summary: |
---|
239 | // render image |
---|
240 | // ctx: |
---|
241 | // a canvas context object |
---|
242 | var s = this.shape; |
---|
243 | if(ctx.pickingMode){ |
---|
244 | ctx.fillRect(s.x, s.y, s.width, s.height); |
---|
245 | }else{ |
---|
246 | this.inherited(arguments); |
---|
247 | } |
---|
248 | }, |
---|
249 | _hitTestGeometry: function(ctx, x, y){ |
---|
250 | // TODO: improve hit testing to take into account transparency |
---|
251 | var s = this.shape; |
---|
252 | return x >= s.x && x <= s.x + s.width && y >= s.y && y <= s.y + s.height ? this : null; |
---|
253 | } |
---|
254 | }); |
---|
255 | |
---|
256 | canvasWithEvents.Text = declare("dojox.gfx.canvasWithEvents.Text", [canvasWithEvents.Shape, canvas.Text], { |
---|
257 | _testInputs: function(ctx, pos){ |
---|
258 | return this._hitTestPixel(ctx, pos); |
---|
259 | } |
---|
260 | }); |
---|
261 | |
---|
262 | canvasWithEvents.Rect = declare("dojox.gfx.canvasWithEvents.Rect", [canvasWithEvents.Shape, canvas.Rect], {}); |
---|
263 | canvasWithEvents.Circle = declare("dojox.gfx.canvasWithEvents.Circle", [canvasWithEvents.Shape, canvas.Circle], {}); |
---|
264 | canvasWithEvents.Ellipse = declare("dojox.gfx.canvasWithEvents.Ellipse", [canvasWithEvents.Shape, canvas.Ellipse],{}); |
---|
265 | canvasWithEvents.Line = declare("dojox.gfx.canvasWithEvents.Line", [canvasWithEvents.Shape, canvas.Line],{}); |
---|
266 | canvasWithEvents.Polyline = declare("dojox.gfx.canvasWithEvents.Polyline", [canvasWithEvents.Shape, canvas.Polyline],{}); |
---|
267 | canvasWithEvents.Path = declare("dojox.gfx.canvasWithEvents.Path", [canvasWithEvents.Shape, canvas.Path],{}); |
---|
268 | canvasWithEvents.TextPath = declare("dojox.gfx.canvasWithEvents.TextPath", [canvasWithEvents.Shape, canvas.TextPath],{}); |
---|
269 | |
---|
270 | // When events are dispatched using on.emit, certain properties of these events (like target) get overwritten by |
---|
271 | // the DOM. The only real way to deal with this at the moment, short of never using any standard event properties, |
---|
272 | // is to store this data out-of-band and fix up the event object passed to the listener by wrapping the listener. |
---|
273 | // The out-of-band data is stored here. |
---|
274 | var fixedEventData = null; |
---|
275 | |
---|
276 | canvasWithEvents.Surface = declare("dojox.gfx.canvasWithEvents.Surface", canvas.Surface, { |
---|
277 | constructor: function(){ |
---|
278 | this._elementUnderPointer = null; |
---|
279 | }, |
---|
280 | |
---|
281 | fixTarget: function(listener){ |
---|
282 | // summary: |
---|
283 | // Corrects the `target` properties of the event object passed to the actual listener. |
---|
284 | // listener: Function |
---|
285 | // An event listener function. |
---|
286 | |
---|
287 | var surface = this; |
---|
288 | |
---|
289 | return function(event){ |
---|
290 | var k; |
---|
291 | if(fixedEventData){ |
---|
292 | if(has("dom-mutableEvents")){ |
---|
293 | Object.defineProperties(event, fixedEventData); |
---|
294 | }else{ |
---|
295 | event = makeFakeEvent(event); |
---|
296 | for(k in fixedEventData){ |
---|
297 | event[k] = fixedEventData[k].value; |
---|
298 | } |
---|
299 | } |
---|
300 | }else{ |
---|
301 | // non-synthetic events need to have target correction too, but since there is no out-of-band |
---|
302 | // data we need to figure out the target ourselves |
---|
303 | var canvas = surface.getEventSource(), |
---|
304 | target = canvas._dojoElementFromPoint( |
---|
305 | // touch events may not be fixed at this point, so clientX/Y may not be set on the |
---|
306 | // event object |
---|
307 | (event.changedTouches ? event.changedTouches[0] : event).pageX, |
---|
308 | (event.changedTouches ? event.changedTouches[0] : event).pageY |
---|
309 | ); |
---|
310 | if(has("dom-mutableEvents")){ |
---|
311 | Object.defineProperties(event, { |
---|
312 | target: { |
---|
313 | value: target, |
---|
314 | configurable: true, |
---|
315 | enumerable: true |
---|
316 | }, |
---|
317 | gfxTarget: { |
---|
318 | value: target.shape, |
---|
319 | configurable: true, |
---|
320 | enumerable: true |
---|
321 | } |
---|
322 | }); |
---|
323 | }else{ |
---|
324 | event = makeFakeEvent(event); |
---|
325 | event.target = target; |
---|
326 | event.gfxTarget = target.shape; |
---|
327 | } |
---|
328 | } |
---|
329 | |
---|
330 | // fixTouchListener in dojo/on undoes target changes by copying everything from changedTouches even |
---|
331 | // if the value already exists on the event; of course, this canvas implementation currently only |
---|
332 | // supports one pointer at a time. if we wanted to make sure all the touches arrays' targets were |
---|
333 | // updated correctly as well, we could support multi-touch and this workaround would not be needed |
---|
334 | if(has("touch")){ |
---|
335 | // some standard properties like clientX/Y are not provided on the main touch event object, |
---|
336 | // so copy them over if we need to |
---|
337 | if(event.changedTouches && event.changedTouches[0]){ |
---|
338 | var changedTouch = event.changedTouches[0]; |
---|
339 | for(k in changedTouch){ |
---|
340 | if(!event[k]){ |
---|
341 | if(has("dom-mutableEvents")){ |
---|
342 | Object.defineProperty(event, k, { |
---|
343 | value: changedTouch[k], |
---|
344 | configurable: true, |
---|
345 | enumerable: true |
---|
346 | }); |
---|
347 | }else{ |
---|
348 | event[k] = changedTouch[k]; |
---|
349 | } |
---|
350 | } |
---|
351 | } |
---|
352 | } |
---|
353 | event.corrected = event; |
---|
354 | } |
---|
355 | |
---|
356 | return listener.call(this, event); |
---|
357 | }; |
---|
358 | }, |
---|
359 | |
---|
360 | _checkPointer: function(event){ |
---|
361 | // summary: |
---|
362 | // Emits enter/leave/over/out events in response to the pointer entering/leaving the inner elements |
---|
363 | // within the canvas. |
---|
364 | |
---|
365 | function emit(types, target, relatedTarget){ |
---|
366 | // summary: |
---|
367 | // Emits multiple synthetic events defined in `types` with the given target `target`. |
---|
368 | |
---|
369 | var oldBubbles = event.bubbles; |
---|
370 | |
---|
371 | for(var i = 0, type; (type = types[i]); ++i){ |
---|
372 | // targets get reset when the event is dispatched so we need to give information to fixTarget to |
---|
373 | // restore the target on the dispatched event through a back channel |
---|
374 | fixedEventData = { |
---|
375 | target: { value: target, configurable: true, enumerable: true}, |
---|
376 | gfxTarget: { value: target.shape, configurable: true, enumerable: true }, |
---|
377 | relatedTarget: { value: relatedTarget, configurable: true, enumerable: true } |
---|
378 | }; |
---|
379 | |
---|
380 | // bubbles can be set directly, though. |
---|
381 | Object.defineProperty(event, "bubbles", { |
---|
382 | value: type.bubbles, |
---|
383 | configurable: true, |
---|
384 | enumerable: true |
---|
385 | }); |
---|
386 | |
---|
387 | on.emit(canvas, type.type, event); |
---|
388 | fixedEventData = null; |
---|
389 | } |
---|
390 | |
---|
391 | Object.defineProperty(event, "bubbles", { value: oldBubbles, configurable: true, enumerable: true }); |
---|
392 | } |
---|
393 | |
---|
394 | // Types must be arrays because hash map order is not guaranteed but we must fire in order to match normal |
---|
395 | // event behaviour |
---|
396 | var TYPES = { |
---|
397 | out: [ |
---|
398 | { type: "mouseout", bubbles: true }, |
---|
399 | { type: "MSPointerOut", bubbles: true }, |
---|
400 | { type: "pointerout", bubbles: true }, |
---|
401 | { type: "mouseleave", bubbles: false }, |
---|
402 | { type: "dojotouchout", bubbles: true} |
---|
403 | ], |
---|
404 | over: [ |
---|
405 | { type: "mouseover", bubbles: true }, |
---|
406 | { type: "MSPointerOver", bubbles: true }, |
---|
407 | { type: "pointerover", bubbles: true }, |
---|
408 | { type: "mouseenter", bubbles: false }, |
---|
409 | { type: "dojotouchover", bubbles: true} |
---|
410 | ] |
---|
411 | }, |
---|
412 | elementUnderPointer = event.target, |
---|
413 | oldElementUnderPointer = this._elementUnderPointer, |
---|
414 | canvas = this.getEventSource(); |
---|
415 | |
---|
416 | if(oldElementUnderPointer !== elementUnderPointer){ |
---|
417 | if(oldElementUnderPointer && oldElementUnderPointer !== canvas){ |
---|
418 | emit(TYPES.out, oldElementUnderPointer, elementUnderPointer); |
---|
419 | } |
---|
420 | |
---|
421 | this._elementUnderPointer = elementUnderPointer; |
---|
422 | |
---|
423 | if(elementUnderPointer && elementUnderPointer !== canvas){ |
---|
424 | emit(TYPES.over, elementUnderPointer, oldElementUnderPointer); |
---|
425 | } |
---|
426 | } |
---|
427 | }, |
---|
428 | |
---|
429 | getEventSource: function(){ |
---|
430 | return this.rawNode; |
---|
431 | }, |
---|
432 | |
---|
433 | on: function(type, listener){ |
---|
434 | // summary: |
---|
435 | // Connects an event to this surface. |
---|
436 | |
---|
437 | return on(this.getEventSource(), type, listener); |
---|
438 | }, |
---|
439 | |
---|
440 | connect: function(/*String*/ name, /*Object*/ object, /*Function|String*/ method){ |
---|
441 | // summary: |
---|
442 | // Deprecated. Connects a handler to an event on this surface. Use `on` instead. |
---|
443 | // name: String |
---|
444 | // The event name |
---|
445 | // object: Object |
---|
446 | // The object that method will receive as "this". |
---|
447 | // method: Function |
---|
448 | // A function reference, or name of a function in context. |
---|
449 | |
---|
450 | if(name.substring(0, 2) == "on"){ |
---|
451 | name = name.substring(2); |
---|
452 | } |
---|
453 | return this.on(name, method ? lang.hitch(object, method) : object); |
---|
454 | }, |
---|
455 | |
---|
456 | disconnect: function(handle){ |
---|
457 | // summary: |
---|
458 | // Deprecated. Disconnects a handler. Use `handle.remove` instead. |
---|
459 | |
---|
460 | handle.remove(); |
---|
461 | }, |
---|
462 | |
---|
463 | _initMirrorCanvas: function(){ |
---|
464 | // summary: |
---|
465 | // Initialises a mirror canvas used for event hit detection. |
---|
466 | |
---|
467 | this._initMirrorCanvas = function(){}; |
---|
468 | |
---|
469 | var canvas = this.getEventSource(), |
---|
470 | mirror = this.mirrorCanvas = canvas.ownerDocument.createElement("canvas"); |
---|
471 | |
---|
472 | mirror.width = 1; |
---|
473 | mirror.height = 1; |
---|
474 | mirror.style.position = "absolute"; |
---|
475 | mirror.style.left = mirror.style.top = "-99999px"; |
---|
476 | canvas.parentNode.appendChild(mirror); |
---|
477 | |
---|
478 | var moveEvt = "mousemove"; |
---|
479 | if(has("pointer-events")){ |
---|
480 | moveEvt = "pointermove"; |
---|
481 | }else if(has("MSPointer")){ |
---|
482 | moveEvt = "MSPointerMove"; |
---|
483 | }else if(has("touch")){ |
---|
484 | moveEvt = "touchmove"; |
---|
485 | } |
---|
486 | on(canvas, moveEvt, lang.hitch(this, "_checkPointer")); |
---|
487 | }, |
---|
488 | |
---|
489 | destroy: function(){ |
---|
490 | if(this.mirrorCanvas){ |
---|
491 | this.mirrorCanvas.parentNode.removeChild(this.mirrorCanvas); |
---|
492 | this.mirrorCanvas = null; |
---|
493 | } |
---|
494 | this.inherited(arguments); |
---|
495 | } |
---|
496 | }); |
---|
497 | |
---|
498 | canvasWithEvents.createSurface = function(parentNode, width, height){ |
---|
499 | // summary: |
---|
500 | // creates a surface (Canvas) |
---|
501 | // parentNode: Node |
---|
502 | // a parent node |
---|
503 | // width: String |
---|
504 | // width of surface, e.g., "100px" |
---|
505 | // height: String |
---|
506 | // height of surface, e.g., "100px" |
---|
507 | |
---|
508 | if(!width && !height){ |
---|
509 | var pos = domGeom.position(parentNode); |
---|
510 | width = width || pos.w; |
---|
511 | height = height || pos.h; |
---|
512 | } |
---|
513 | if(typeof width === "number"){ |
---|
514 | width = width + "px"; |
---|
515 | } |
---|
516 | if(typeof height === "number"){ |
---|
517 | height = height + "px"; |
---|
518 | } |
---|
519 | |
---|
520 | var surface = new canvasWithEvents.Surface(), |
---|
521 | parent = dom.byId(parentNode), |
---|
522 | canvas = parent.ownerDocument.createElement("canvas"); |
---|
523 | |
---|
524 | canvas.width = g.normalizedLength(width); // in pixels |
---|
525 | canvas.height = g.normalizedLength(height); // in pixels |
---|
526 | |
---|
527 | parent.appendChild(canvas); |
---|
528 | surface.rawNode = canvas; |
---|
529 | surface._parent = parent; |
---|
530 | surface.surface = surface; |
---|
531 | |
---|
532 | g._base._fixMsTouchAction(surface); |
---|
533 | |
---|
534 | // any event handler added to the canvas needs to have its target fixed. |
---|
535 | var oldAddEventListener = canvas.addEventListener, |
---|
536 | oldRemoveEventListener = canvas.removeEventListener, |
---|
537 | listeners = []; |
---|
538 | |
---|
539 | var addEventListenerImpl = function(type, listener, useCapture){ |
---|
540 | surface._initMirrorCanvas(); |
---|
541 | |
---|
542 | var actualListener = surface.fixTarget(listener); |
---|
543 | listeners.push({ original: listener, actual: actualListener }); |
---|
544 | oldAddEventListener.call(this, type, actualListener, useCapture); |
---|
545 | }; |
---|
546 | var removeEventListenerImpl = function(type, listener, useCapture){ |
---|
547 | for(var i = 0, record; (record = listeners[i]); ++i){ |
---|
548 | if(record.original === listener){ |
---|
549 | oldRemoveEventListener.call(this, type, record.actual, useCapture); |
---|
550 | listeners.splice(i, 1); |
---|
551 | break; |
---|
552 | } |
---|
553 | } |
---|
554 | }; |
---|
555 | try{ |
---|
556 | Object.defineProperties(canvas, { |
---|
557 | addEventListener: { |
---|
558 | value: addEventListenerImpl, |
---|
559 | enumerable: true, |
---|
560 | configurable: true |
---|
561 | }, |
---|
562 | removeEventListener: { |
---|
563 | value: removeEventListenerImpl |
---|
564 | } |
---|
565 | }); |
---|
566 | }catch(e){ |
---|
567 | // Object.defineProperties fails on iOS 4-5. "Not supported on DOM objects"). |
---|
568 | canvas.addEventListener = addEventListenerImpl; |
---|
569 | canvas.removeEventListener = removeEventListenerImpl; |
---|
570 | } |
---|
571 | |
---|
572 | |
---|
573 | canvas._dojoElementFromPoint = function(x, y){ |
---|
574 | // summary: |
---|
575 | // Returns the shape under the given (x, y) coordinate. |
---|
576 | // evt: |
---|
577 | // mouse event |
---|
578 | |
---|
579 | if(!surface.mirrorCanvas){ |
---|
580 | return this; |
---|
581 | } |
---|
582 | |
---|
583 | var surfacePosition = domGeom.position(this, true); |
---|
584 | |
---|
585 | // use canvas-relative positioning |
---|
586 | x -= surfacePosition.x; |
---|
587 | y -= surfacePosition.y; |
---|
588 | |
---|
589 | var mirror = surface.mirrorCanvas, |
---|
590 | ctx = mirror.getContext("2d"), |
---|
591 | children = surface.children; |
---|
592 | |
---|
593 | ctx.clearRect(0, 0, mirror.width, mirror.height); |
---|
594 | ctx.save(); |
---|
595 | ctx.strokeStyle = "rgba(127,127,127,1.0)"; |
---|
596 | ctx.fillStyle = "rgba(127,127,127,1.0)"; |
---|
597 | ctx.pickingMode = true; |
---|
598 | |
---|
599 | // TODO: Make inputs non-array |
---|
600 | var inputs = [ { x: x, y: y } ]; |
---|
601 | |
---|
602 | // process the inputs to find the target. |
---|
603 | for(var i = children.length - 1; i >= 0; i--){ |
---|
604 | children[i]._testInputs(ctx, inputs); |
---|
605 | |
---|
606 | if(inputs[0].target){ |
---|
607 | break; |
---|
608 | } |
---|
609 | } |
---|
610 | ctx.restore(); |
---|
611 | return inputs[0] && inputs[0].target ? inputs[0].target.rawNode : this; |
---|
612 | }; |
---|
613 | |
---|
614 | |
---|
615 | return surface; // dojox/gfx.Surface |
---|
616 | }; |
---|
617 | |
---|
618 | var Creator = { |
---|
619 | createObject: function(){ |
---|
620 | // summary: |
---|
621 | // Creates a synthetic, partially-interoperable Element object used to uniquely identify the given |
---|
622 | // shape within the canvas pseudo-DOM. |
---|
623 | |
---|
624 | var shape = this.inherited(arguments), |
---|
625 | listeners = {}; |
---|
626 | |
---|
627 | shape.rawNode = { |
---|
628 | shape: shape, |
---|
629 | ownerDocument: shape.surface.rawNode.ownerDocument, |
---|
630 | parentNode: shape.parent ? shape.parent.rawNode : null, |
---|
631 | addEventListener: function(type, listener){ |
---|
632 | var listenersOfType = listeners[type] = (listeners[type] || []); |
---|
633 | for(var i = 0, record; (record = listenersOfType[i]); ++i){ |
---|
634 | if(record.listener === listener){ |
---|
635 | return; |
---|
636 | } |
---|
637 | } |
---|
638 | |
---|
639 | listenersOfType.push({ |
---|
640 | listener: listener, |
---|
641 | handle: aspect.after(this, "on" + type, shape.surface.fixTarget(listener), true) |
---|
642 | }); |
---|
643 | }, |
---|
644 | removeEventListener: function(type, listener){ |
---|
645 | var listenersOfType = listeners[type]; |
---|
646 | if(!listenersOfType){ |
---|
647 | return; |
---|
648 | } |
---|
649 | for(var i = 0, record; (record = listenersOfType[i]); ++i){ |
---|
650 | if(record.listener === listener){ |
---|
651 | record.handle.remove(); |
---|
652 | listenersOfType.splice(i, 1); |
---|
653 | return; |
---|
654 | } |
---|
655 | } |
---|
656 | } |
---|
657 | }; |
---|
658 | return shape; |
---|
659 | } |
---|
660 | }; |
---|
661 | |
---|
662 | canvasWithEvents.Group.extend(Creator); |
---|
663 | canvasWithEvents.Surface.extend(Creator); |
---|
664 | |
---|
665 | return canvasWithEvents; |
---|
666 | }); |
---|