[483] | 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 | }); |
---|