diff --git a/examples/Animated/WineGums.html b/examples/Animated/WineGums.html new file mode 100644 index 00000000..650fa20c --- /dev/null +++ b/examples/Animated/WineGums.html @@ -0,0 +1,150 @@ + + + + + Wine Gums + + + + + + + + \ No newline at end of file diff --git a/examples/SVG Import/Arcs.html b/examples/SVG Import/Arcs.html new file mode 100644 index 00000000..ee600c17 --- /dev/null +++ b/examples/SVG Import/Arcs.html @@ -0,0 +1,76 @@ + + + + + Arcs Testing + + + + + + + + + + + + + + + + Arc start + Arc end + + + + + + + + + + large-arc-flag=0 + sweep-flag=0 + + + + + large-arc-flag=0 + sweep-flag=1 + + + + + large-arc-flag=1 + sweep-flag=0 + + + + + large-arc-flag=1 + sweep-flag=1 + + + + + + + + + \ No newline at end of file diff --git a/src/basic/Point.js b/src/basic/Point.js index 8714e417..d84dc79c 100644 --- a/src/basic/Point.js +++ b/src/basic/Point.js @@ -673,7 +673,7 @@ var Point = Base.extend(/** @lends Point# */{ c = Math.cos(angle); point = new Point( point.x * c - point.y * s, - point.y * c + point.x * s + point.x * s + point.y * c ); return center ? point.add(center) : point; }, diff --git a/src/core/Base.js b/src/core/Base.js index a79e655d..0d112b61 100644 --- a/src/core/Base.js +++ b/src/core/Base.js @@ -201,6 +201,13 @@ Base.inject(/** @lends Base# */{ return list[list.__index = start || list.__index || 0]; }, + /** + * Returns how many arguments remain to be read in the argument list. + */ + remain: function(list) { + return list.length - (list.__index || 0); + }, + /** * Reads all readable arguments from the list, handling nested arrays * separately. diff --git a/src/dom/DomElement.js b/src/dom/DomElement.js index 624df695..9c66ec22 100644 --- a/src/dom/DomElement.js +++ b/src/dom/DomElement.js @@ -46,6 +46,24 @@ var DomElement = new function() { return res; } + // Handles both getting and setting of vendor prefix values + function handlePrefix(el, name, set, value) { + var prefixes = ['webkit', 'moz', 'Moz', 'ms', 'o', ''], + suffix = name[0].toUpperCase() + name.substring(1); + for (var i = 0; i < 6; i++) { + var prefix = prefixes[i], + key = prefix ? prefix + suffix : name; + if (key in el) { + if (set) { + el[key] = value; + } else { + return el[key]; + } + break; + } + } + } + return /** @lends DomElement */{ create: function(nodes, parent) { var isArray = Array.isArray(nodes), @@ -203,13 +221,17 @@ var DomElement = new function() { * Gets the given property from the element, trying out all browser * prefix variants. */ - getPrefixValue: function(el, name) { - var value = el[name], - prefixes = ['webkit', 'moz', 'ms', 'o'], - suffix = name[0].toUpperCase() + name.substring(1); - for (var i = 0; i < 4 && value == null; i++) - value = el[prefixes[i] + suffix]; - return value; + getPrefixed: function(el, name) { + return handlePrefix(el, name); + }, + + setPrefixed: function(el, name, value) { + if (typeof name === 'object') { + for (var key in name) + handlePrefix(el, key, true, name[key]); + } else { + handlePrefix(el, name, true, value); + } } }; }; diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index 3426edfa..33dc0bbb 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -17,13 +17,21 @@ */ var DomEvent = /** @lends DomEvent */{ add: function(el, events) { - for (var type in events) - el.addEventListener(type, events[type], false); + for (var type in events) { + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.addEventListener(parts[i], func, false); + } }, remove: function(el, events) { - for (var type in events) - el.removeEventListener(type, events[type], false); + for (var type in events) { + var func = events[type], + parts = type.split(/[\s,]+/g); + for (var i = 0, l = parts.length; i < l; i++) + el.removeEventListener(parts[i], func, false); + } }, getPoint: function(event) { @@ -59,8 +67,7 @@ var DomEvent = /** @lends DomEvent */{ }; DomEvent.requestAnimationFrame = new function() { - var nativeRequest = DomElement.getPrefixValue(window, - 'requestAnimationFrame'), + var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'), requested = false, callbacks = [], focused = true, diff --git a/src/path/Path.js b/src/path/Path.js index 3888c3a2..ed265fba 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -2295,58 +2295,126 @@ var Path = PathItem.extend(/** @lends Path# */{ this.quadraticCurveTo(handle, to); }, - arcTo: function(/* to, clockwise | through, to */) { + arcTo: function(/* to, clockwise | through, to + | to, radius, rotation, large, sweep */) { // Get the start point: var current = getCurrentSegment(this), from = current._point, - through, to = Point.read(arguments), - // Peek at next value to see if it's clockwise, - // with true as default value. - clockwise = Base.pick(Base.peek(arguments), true); + through, + // Peek at next value to see if it's clockwise, with true as the + // default value. + peek = Base.peek(arguments), + clockwise = Base.pick(peek, true), + center, extent, vector, matrix; + // We're handling three different approaches to drawing arcs in one + // large function: if (typeof clockwise === 'boolean') { - // arcTo(to, clockwise) + // #1: arcTo(to, clockwise) var middle = from.add(to).divide(2), through = middle.add(middle.subtract(from).rotate( clockwise ? -90 : 90)); - } else { - // arcTo(through, to) + } else if (Base.remain(arguments) <= 2) { + // #2: arcTo(through, to) through = to; to = Point.read(arguments); + } else { + // #3: arcTo(to, radius, rotation, large, sweep) + // Drawing arcs in SVG style: + var radius = Size.read(arguments); + // If rx = 0 or ry = 0 then this arc is treated as a + // straight line joining the endpoints. + if (radius.isZero()) + return this.lineTo(to); + // See for an explanation of the following calculations: + // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + var rotation = Base.read(arguments), + large = !!Base.read(arguments), + sweep = !!Base.read(arguments), + middle = from.add(to).divide(2), + pt = from.subtract(middle).rotate(-rotation), + x = pt.x, + y = pt.y, + abs = Math.abs, + EPSILON = /*#=*/ Numerical.EPSILON, + rx = abs(radius.width), + ry = abs(radius.height), + rxSq = rx * rx, + rySq = ry * ry, + xSq = x * x, + ySq = y * y; + // "...ensure radii are large enough" + var factor = Math.sqrt(xSq / rxSq + ySq / rySq); + if (factor > 1) { + rx *= factor; + ry *= factor; + rxSq = rx * rx; + rySq = ry * ry; + } + factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) / + (rxSq * ySq + rySq * xSq); + if (abs(factor) < EPSILON) + factor = 0; + if (factor < 0) + throw new Error( + 'Cannot create an arc with the given arguments'); + center = new Point(rx * y / ry, -ry * x / rx) + // "...where the + sign is chosen if fA != fS, + // and the − sign is chosen if fA = fS." + .multiply((large == sweep ? -1 : 1) * Math.sqrt(factor)) + .rotate(rotation).add(middle); + // Now create a matrix that maps the unit circle to the ellipse, + // for easier construction below. + matrix = new Matrix().translate(center).rotate(rotation) + .scale(rx, ry); + // Transform from and to to the unit circle coordinate space + // and calculcate start vector and extend from there. + vector = matrix._inverseTransform(from); + extent = vector.getDirectedAngle(matrix._inverseTransform(to)); + // "...in other words, if sweep = 0 and extent is > 0, subtract + // 360, whereas if sweep = 1 and extent < 0, then add 360." + if (!sweep && extent > 0) + extent -= 360; + else if (sweep && extent < 0) + extent += 360; } - // Construct the two perpendicular middle lines to (from, through) - // and (through, to), and intersect them to get the center - var l1 = new Line(from.add(through).divide(2), - through.subtract(from).rotate(90), true), - l2 = new Line(through.add(to).divide(2), - to.subtract(through).rotate(90), true), - center = l1.intersect(l2, true), - line = new Line(from, to), - throughSide = line.getSide(through); - if (!center) { + if (through) { + // Calculate center, vector and extend for non SVG versions: + // Construct the two perpendicular middle lines to + // (from, through) and (through, to), and intersect them to get + // the center. + var l1 = new Line(from.add(through).divide(2), + through.subtract(from).rotate(90), true), + l2 = new Line(through.add(to).divide(2), + to.subtract(through).rotate(90), true), + line = new Line(from, to), + throughSide = line.getSide(through); + center = l1.intersect(l2, true); // If the two lines are colinear, there cannot be an arc as the // circle is infinitely big and has no center point. If side is // 0, the connecting arc line of this huge circle is a line // between the two points, so we can use #lineTo instead. // Otherwise we bail out: - if (!throughSide) - return this.lineTo(to); - throw new Error('Cannot put an arc through the given points: ' - + [from, through, to]); - } - var vector = from.subtract(center), - extent = vector.getDirectedAngle(to.subtract(center)), - centerSide = line.getSide(center); - if (centerSide == 0) { - // If the center is lying on the line, we might have gotten the - // wrong sign for extent above. Use the sign of the side of the - // through point. - extent = throughSide * Math.abs(extent); - } else if (throughSide == centerSide) { - // If the center is on the same side of the line (from, to) as - // the through point, we're extending bellow 180 degrees and - // need to adapt extent. - extent -= 360 * (extent < 0 ? -1 : 1); + if (!center) { + if (!throughSide) + return this.lineTo(to); + throw new Error( + 'Cannot create an arc with the given arguments'); + } + vector = from.subtract(center); + extent = vector.getDirectedAngle(to.subtract(center)); + var centerSide = line.getSide(center); + if (centerSide === 0) { + // If the center is lying on the line, we might have gotten + // the wrong sign for extent above. Use the sign of the side + // of the through point. + extent = throughSide * Math.abs(extent); + } else if (throughSide === centerSide) { + // If the center is on the same side of the line (from, to) + // as the through point, we're extending bellow 180 degrees + // and need to adapt extent. + extent -= 360 * (extent < 0 ? -1 : 1); + } } var ext = Math.abs(extent), count = ext >= 360 ? 4 : Math.ceil(ext / 90), @@ -2357,15 +2425,29 @@ var Path = PathItem.extend(/** @lends Path# */{ for (var i = 0; i <= count; i++) { // Explicitely use to point for last segment, since depending // on values the calculation adds imprecision: - var pt = i < count ? center.add(vector) : to; - var out = i < count ? vector.rotate(90).multiply(z) : null; - if (i == 0) { + var pt = to, + out = null; + if (i < count) { + out = vector.rotate(90).multiply(z); + if (matrix) { + pt = matrix._transformPoint(vector); + out = matrix._transformPoint(vector.add(out)) + .subtract(pt); + } else { + pt = center.add(vector); + } + } + if (i === 0) { // Modify startSegment current.setHandleOut(out); } else { // Add new Segment - segments.push( - new Segment(pt, vector.rotate(-90).multiply(z), out)); + var _in = vector.rotate(-90).multiply(z); + if (matrix) { + _in = matrix._transformPoint(vector.add(_in)) + .subtract(pt); + } + segments.push(new Segment(pt, _in, out)); } vector = vector.rotate(inc); } diff --git a/src/path/PathItem.Boolean.js b/src/path/PathItem.Boolean.js index a85f2765..dec87986 100644 --- a/src/path/PathItem.Boolean.js +++ b/src/path/PathItem.Boolean.js @@ -214,7 +214,9 @@ PathItem.inject(new function() { var loc = intersections[i], t = loc._parameter; // Check if we are splitting same curve multiple times - if (prevLoc && prevLoc._curve === loc._curve) { + if (prevLoc && prevLoc._curve === loc._curve + // Avoid dividing with zero + && prevLoc._parameter > 0) { // Scale parameter after previous split. t /= prevLoc._parameter; } else { @@ -404,8 +406,8 @@ PathItem.inject(new function() { seg = interSeg; dir = 1; } else if (w3 * w4 !== 0) { - // Do not attempt to switch contours if we aren't absolutely - // sure that there is a possible candidate. + // Do not attempt to switch contours if we aren't + // absolutely sure that there is a possible candidate. var curve = w3 < w4 ? c3 : c4, nextCurve = operator(curve._segment1._winding) ? curve @@ -469,8 +471,8 @@ PathItem.inject(new function() { * * @param {Point} point the location for which to determine the winding * direction - * @param {Boolean} horizontal whether we need to consider this point as - * part of a horizontal curve + * @param {Boolean} horizontal whether we need to consider this point + * as part of a horizontal curve * @param {Boolean} testContains whether we need to consider this point * as part of stationary points on the curve itself, used when checking * the winding about a point. @@ -665,4 +667,4 @@ CompoundPath.inject(/** @lends CompoundPath# */{ monoCurves.push.apply(monoCurves, children[i]._getMonoCurves()); return monoCurves; } -}); \ No newline at end of file +}); diff --git a/src/path/PathItem.js b/src/path/PathItem.js index f2e5b3a9..12bf2453 100644 --- a/src/path/PathItem.js +++ b/src/path/PathItem.js @@ -192,10 +192,11 @@ var PathItem = Item.extend(/** @lends PathItem# */{ relative = false, previous, control, - current = new Point(); + current = new Point(), + start = new Point(); function getCoord(index, coord) { - var val = parseFloat(coords[index]); + var val = +coords[index]; if (relative) val += current[coord]; return val; @@ -219,6 +220,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{ coords = part.match(/[+-]?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?/g); var length = coords && coords.length; relative = command === lower; + if (previous === 'z' && lower !== 'z') + this.moveTo(current = start); switch (lower) { case 'm': case 'l': @@ -226,6 +229,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{ this[j === 0 && lower === 'm' ? 'moveTo' : 'lineTo']( current = getPoint(j)); control = current; + if(lower == 'm') + start = current; break; case 'h': case 'v': @@ -248,12 +253,12 @@ var PathItem = Item.extend(/** @lends PathItem# */{ // Smooth cubicCurveTo for (var j = 0; j < length; j += 4) { this.cubicCurveTo( - /[cs]/i.test(previous) + /[cs]/.test(previous) ? current.multiply(2).subtract(control) : current, control = getPoint(j), current = getPoint(j + 2)); - previous = command; + previous = lower; } break; case 'q': @@ -266,23 +271,26 @@ var PathItem = Item.extend(/** @lends PathItem# */{ case 't': // Smooth quadraticCurveTo for (var j = 0; j < length; j += 2) { - console.log(previous, /[qt]/i.test(previous)); this.quadraticCurveTo( - control = (/[qt]/i.test(previous) + control = (/[qt]/.test(previous) ? current.multiply(2).subtract(control) : current), current = getPoint(j)); - previous = command; + previous = lower; } break; case 'a': - // TODO: Implement Arcs! + for (var j = 0; j < length; j += 7) { + this.arcTo(current = getPoint(j + 5), + new Size(+coords[0], +coords[1]), + +coords[2], +coords[3], +coords[4]); + } break; case 'z': this.closePath(); break; } - previous = command; + previous = lower; } }, diff --git a/src/path/Segment.js b/src/path/Segment.js index 13345d38..6a5d7b85 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -150,25 +150,27 @@ var Segment = Base.extend(/** @lends Segment# */{ }, _changed: function(point) { - if (!this._path) + var path = this._path; + if (!path) return; - // Delegate changes to affected curves if they exist. Check _curves - // first to make sure we're not creating it by calling this.getCurve(). - var curve = this._path._curves && this.getCurve(), - other; - if (curve) { - curve._changed(); - // Get the other affected curve, which is the previous one for - // _point or _handleIn changing when this segment is _segment1 of - // the curve, for all other cases it's the next (e.g. _handleOut - // when this segment is _segment2) - if (other = (curve[point == this._point - || point == this._handleIn && curve._segment1 == this - ? 'getPrevious' : 'getNext']())) { - other._changed(); - } + // Delegate changes to affected curves if they exist. + var curves = path._curves, + index = this._index, + curveIn, curveOut; + if (curves) { + // Updated the neighboring affected curves, depending on which point + // is changing. + // TODO: Consider exposing these curves too, through #curveIn, + // and #curveOut, next to #curve? + if ((!point || point === this._point || point === this._handleIn) + && (curveIn = curves[index - 1] + || path._closed && curves[curves.length - 1])) + curveIn._changed(); + if ((!point || point === this._point || point === this._handleOut) + && (curveOut = curves[index])) + curveOut._changed(); } - this._path._changed(/*#=*/ Change.GEOMETRY); + path._changed(/*#=*/ Change.GEOMETRY); }, /** @@ -373,7 +375,8 @@ var Segment = Base.extend(/** @lends Segment# */{ }, /** - * The curve that the segment belongs to. + * The curve that the segment belongs to. For the last segment of an open + * path, the previous segment is returned. * * @type Curve * @bean @@ -474,6 +477,16 @@ var Segment = Base.extend(/** @lends Segment# */{ return '{ ' + parts.join(', ') + ' }'; }, + /** + * Transform the segment by the specified matrix. + * + * @param {Matrix} matrix the matrix to transform the segment by + */ + transform: function(matrix) { + this._transformCoordinates(matrix, new Array(6), true); + this._changed(); + }, + _transformCoordinates: function(matrix, coords, change) { // Use matrix.transform version() that takes arrays of multiple // points for largely improved performance, as no calls to diff --git a/src/style/Color.js b/src/style/Color.js index 23c5438b..0ab82def 100644 --- a/src/style/Color.js +++ b/src/style/Color.js @@ -71,7 +71,7 @@ var Color = Base.extend(new function() { // RGB / RGBA components = match[1].split(','); for (var i = 0, l = components.length; i < l; i++) { - var value = parseFloat(components[i]); + var value = +components[i]; components[i] = i < 3 ? value / 255 : value; } } else { diff --git a/src/svg/SVGImport.js b/src/svg/SVGImport.js index 1dc82912..a1656e63 100644 --- a/src/svg/SVGImport.js +++ b/src/svg/SVGImport.js @@ -130,14 +130,10 @@ new function() { } function importPath(node) { - // Get the path data, and determine whether it is a compound path or a - // normal path based on the amount of moveTo commands inside it. - var data = node.getAttribute('d'), - path = data.match(/m/gi).length > 1 - ? new CompoundPath() - : new Path(); - path.setPathData(data); - return path; + return new CompoundPath({ + pathData: node.getAttribute('d'), + insert: false + }).reduce(); } function importGradient(node, type) { diff --git a/src/ui/CanvasView.js b/src/ui/CanvasView.js index 4923aafc..ed88d459 100644 --- a/src/ui/CanvasView.js +++ b/src/ui/CanvasView.js @@ -51,7 +51,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{ // Hi-DPI Canvas support based on: // http://www.html5rocks.com/en/tutorials/canvas/hidpi/ var deviceRatio = window.devicePixelRatio || 1, - backingStoreRatio = DomElement.getPrefixValue(this._context, + backingStoreRatio = DomElement.getPrefixed(this._context, 'backingStorePixelRatio') || 1; this._pixelRatio = deviceRatio / backingStoreRatio; } diff --git a/src/ui/View.js b/src/ui/View.js index b1a1c753..6a8d2439 100644 --- a/src/ui/View.js +++ b/src/ui/View.js @@ -38,7 +38,20 @@ var View = Base.extend(Callback, /** @lends View# */{ if (this._id == null) element.setAttribute('id', this._id = 'view-' + View._id++); // Install event handlers - DomEvent.add(element, this._viewHandlers); + DomEvent.add(element, this._viewEvents); + // Borrowed from Hammer.js: + var none = 'none'; + DomElement.setPrefixed(element.style, { + userSelect: none, + // This makes the element blocking in IE10+ + // You could experiment with the value, see this issue: + // https://github.com/EightMedia/hammer.js/issues/241 + touchAction: none, + touchCallout: none, + contentZooming: none, + userDrag: none, + tapHighlightColor: 'rgba(0,0,0,0)' + }); // If the element has the resize attribute, resize the it to fill the // window and resize it again whenever the user resizes the window. if (PaperScope.hasAttribute(element, 'resize')) { @@ -48,7 +61,7 @@ var View = Base.extend(Callback, /** @lends View# */{ that = this; size = DomElement.getViewportBounds(element) .getSize().subtract(offset); - this._windowHandlers = { + this._windowEvents = { resize: function() { // Only update element offset if it's not invisible, as // otherwise the offset would be wrong. @@ -60,7 +73,7 @@ var View = Base.extend(Callback, /** @lends View# */{ .getSize().subtract(offset)); } }; - DomEvent.add(window, this._windowHandlers); + DomEvent.add(window, this._windowEvents); } else { // Try visible size first, since that will help handling previously // scaled canvases (e.g. when dealing with pixel-ratio) @@ -130,8 +143,8 @@ var View = Base.extend(Callback, /** @lends View# */{ this._project.view = null; /*#*/ if (__options.environment == 'browser') { // Uninstall event handlers again for this view. - DomEvent.remove(this._element, this._viewHandlers); - DomEvent.remove(window, this._windowHandlers); + DomEvent.remove(this._element, this._viewEvents); + DomEvent.remove(window, this._windowEvents); /*#*/ } // __options.environment == 'browser' this._element = this._project = null; // Remove all onFrame handlers. @@ -687,7 +700,68 @@ var View = Base.extend(Callback, /** @lends View# */{ } } - function mousedown(event) { + function handleMouseMove(view, point, event) { + view._handleEvent('mousemove', point, event); + var tool = view._scope.tool; + if (tool) { + // If there's no onMouseDrag, fire onMouseMove while dragging. + tool._handleEvent(dragging && tool.responds('mousedrag') + ? 'mousedrag' : 'mousemove', point, event); + } + view.update(); + return tool; + } + + // Touch handling inspired by Hammer.js + var navigator = window.navigator, + mousedown, mousemove, mouseup; + if (navigator.pointerEnabled || navigator.msPointerEnabled) { + // HTML5 / MS pointer events + mousedown = 'pointerdown MSPointerDown'; + mousemove = 'pointermove MSPointerMove'; + mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel'; + } else { + mousedown = 'touchstart'; + mousemove = 'touchmove'; + mouseup = 'touchend touchcancel'; + // Do not add mouse events on mobile and tablet devices + if (!('ontouchstart' in window && navigator.userAgent.match( + /mobile|tablet|ip(ad|hone|od)|android|silk/i))) { + // For non pointer events browsers and mixed browsers, like chrome + // on Windows8 touch laptop. + mousedown += ' mousedown'; + mousemove += ' mousemove'; + mouseup += ' mouseup'; + } + } + + var viewEvents = { + 'selectstart dragstart': function(event) { + // Only stop this even if we're dragging already, since otherwise no + // text whatsoever can be selected on the page. + if (dragging) + event.preventDefault(); + } + }; + + var docEvents = { + mouseout: function(event) { + // When the moues leaves the document, fire one last mousemove + // event, to give items the change to receive a mouseleave, etc. + var view = View._focused, + target = DomEvent.getRelatedTarget(event); + if (view && (!target || target.nodeName === 'HTML')) + handleMouseMove(view, viewToProject(view, event), event); + }, + + scroll: updateFocus + }; + + // mousemove and mouseup events need to be installed on document, not the + // view element, since we want to catch the end of drag events even outside + // our view. Only the mousedown events are installed on the view, as defined + // by _viewEvents below. + viewEvents[mousedown] = function(event) { // Get the view from the event, and store a reference to the view that // should receive keyboard input. var view = View._focused = getView(event), @@ -701,29 +775,17 @@ var View = Base.extend(Callback, /** @lends View# */{ // In the end we always call update(), which only updates the view if // anything has changed in the above calls. view.update(); - } + }; - function handleMouseMove(view, point, event) { - view._handleEvent('mousemove', point, event); - var tool = view._scope.tool; - if (tool) { - // If there's no onMouseDrag, fire onMouseMove while dragging. - tool._handleEvent(dragging && tool.responds('mousedrag') - ? 'mousedrag' : 'mousemove', point, event); - } - view.update(); - return tool; - } - - function mousemove(event) { + docEvents[mousemove] = function(event) { var view = View._focused; if (!dragging) { // See if we can get the view from the current event target, and // handle the mouse move over it. var target = getView(event); if (target) { - // Temporarily focus this view without making it sticky, so - // Key events are handled too during the mouse over + // Temporarily focus this view without making it sticky, so Key + // events are handled too during the mouse over. // If we switch view, fire one last mousemove in the old view, // to give items the change to receive a mouseleave, etc. if (view !== target) @@ -741,18 +803,9 @@ var View = Base.extend(Callback, /** @lends View# */{ if (dragging || view.getBounds().contains(point)) tool = handleMouseMove(view, point, event); } - } + }; - function mouseout(event) { - // When the moues leaves the document, fire one last mousemove event, - // to give items the change to receive a mouseleave, etc. - var view = View._focused, - target = DomEvent.getRelatedTarget(event); - if (view && (!target || target.nodeName === 'HTML')) - handleMouseMove(view, viewToProject(view, event), event); - } - - function mouseup(event) { + docEvents[mouseup] = function(event) { var view = View._focused; if (!view || !dragging) return; @@ -763,40 +816,16 @@ var View = Base.extend(Callback, /** @lends View# */{ if (tool) tool._handleEvent('mouseup', point, event); view.update(); - } + }; - function selectstart(event) { - // Only stop this even if we're dragging already, since otherwise no - // text whatsoever can be selected on the page. - if (dragging) - event.preventDefault(); - } - - // mousemove and mouseup events need to be installed on document, not the - // view element, since we want to catch the end of drag events even outside - // our view. Only the mousedown events are installed on the view, as handled - // by _createHandlers below. - - DomEvent.add(document, { - mousemove: mousemove, - mouseout: mouseout, - mouseup: mouseup, - touchmove: mousemove, - touchend: mouseup, - selectstart: selectstart, - scroll: updateFocus - }); + DomEvent.add(document, docEvents); DomEvent.add(window, { load: updateFocus }); return { - _viewHandlers: { - mousedown: mousedown, - touchstart: mousedown, - selectstart: selectstart - }, + _viewEvents: viewEvents, // To be defined in subclasses _handleEvent: function(/* type, point, event */) {},