Merge branch 'refs/heads/master' into apply-matrix

This commit is contained in:
Jürg Lehni 2014-03-13 00:54:05 +01:00
commit 3c257dcae0
14 changed files with 550 additions and 158 deletions

View file

@ -0,0 +1,150 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Wine Gums</title>
<link rel="stylesheet" href="../css/style.css">
<script type="text/javascript" src="../../dist/paper.js"></script>
<script type="text/paperscript" canvas="canvas">
// kynd.info 2014
function Ball(r, p, v) {
this.radius = r;
this.point = p;
this.vector = v;
this.maxVec = 15;
this.numSegment = Math.floor(r / 3 + 2);
this.boundOffset = [];
this.boundOffsetBuff = [];
this.sidePoints = [];
this.path = new Path({
fillColor: {
hue: Math.random() * 360,
saturation: 1,
brightness: 1
},
blendMode: 'screen'
});
for (var i = 0; i < this.numSegment; i ++) {
this.boundOffset.push(this.radius);
this.boundOffsetBuff.push(this.radius);
this.path.add(new Point());
this.sidePoints.push(new Point({
angle: 360 / this.numSegment * i,
length: 1
}));
}
}
Ball.prototype = {
iterate: function() {
this.checkWallCollision();
if (this.vector.length > this.maxVec)
this.vector.length = this.maxVec;
this.point += this.vector;
this.updateShape();
},
checkWallCollision: function() {
var size = view.size;
if (this.point.x < -this.radius)
this.point.x = size.width + this.radius;
if (this.point.x > size.width + this.radius)
this.point.x = -this.radius;
if (this.point.y < -this.radius)
this.point.y = size.height + this.radius;
if (this.point.y > size.height + this.radius)
this.point.y = -this.radius;
},
updateShape: function() {
var segments = this.path.segments;
for (var i = 0; i < this.numSegment; i ++)
segments[i].point = this.getSidePoint(i);
this.path.smooth();
for (var i = 0; i < this.numSegment; i ++) {
if (this.boundOffset[i] < this.radius / 4)
this.boundOffset[i] = this.radius / 4;
var next = (i + 1) % this.numSegment;
var prev = (i > 0) ? i - 1 : this.numSegment - 1;
var offset = this.boundOffset[i];
offset += (this.radius - offset) / 15;
offset += ((this.boundOffset[next] + this.boundOffset[prev]) / 2 - offset) / 3;
this.boundOffsetBuff[i] = this.boundOffset[i] = offset;
}
},
react: function(b) {
var dist = this.point.getDistance(b.point);
if (dist < this.radius + b.radius && dist != 0) {
var overlap = this.radius + b.radius - dist;
var direc = (this.point - b.point).normalize(overlap * 0.015);
this.vector += direc;
b.vector -= direc;
this.calcBounds(b);
b.calcBounds(this);
this.updateBounds();
b.updateBounds();
}
},
getBoundOffset: function(b) {
var diff = this.point - b;
var angle = (diff.angle + 180) % 360;
return this.boundOffset[Math.floor(angle / 360 * this.boundOffset.length)];
},
calcBounds: function(b) {
for (var i = 0; i < this.numSegment; i ++) {
var tp = this.getSidePoint(i);
var bLen = b.getBoundOffset(tp);
var td = tp.getDistance(b.point);
if (td < bLen) {
this.boundOffsetBuff[i] -= (bLen - td) / 2;
}
}
},
getSidePoint: function(index) {
return this.point + this.sidePoints[index] * this.boundOffset[index];
},
updateBounds: function() {
for (var i = 0; i < this.numSegment; i ++)
this.boundOffset[i] = this.boundOffsetBuff[i];
}
};
//--------------------- main ---------------------
var balls = [];
var numBalls = 18;
for (var i = 0; i < numBalls; i++) {
var position = Point.random() * view.size;
var vector = new Point({
angle: 360 * Math.random(),
length: Math.random() * 10
});
balls.push(new Ball(Math.random() * 60 + 60, position, vector));
}
function onFrame() {
for (var i = 0; i < balls.length - 1; i++) {
for (var j = i + 1; j < balls.length; j++) {
balls[i].react(balls[j]);
}
}
for (var i = 0, l = balls.length; i < l; i++) {
balls[i].iterate();
}
}
</script>
</head>
<body>
<canvas id="canvas" resize hidpi="off" style="background:black"></canvas>
</body>
</html>

View file

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Arcs Testing</title>
<script type="text/javascript" src="../../dist/paper.js"></script>
<script type="text/paperscript" canvas="canvas">
project.importSVG(document.getElementById('svg'));
</script>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="1000" height="500" id="svg">
<g transform="scale(0.5)">
<path d="M300,200 h-150 a150,150 0 1,0 150,-150 z"
fill="red" stroke="blue" stroke-width="5" />
<path d="M275,175 v-150 a150,150 0 0,0 -150,150 z"
fill="yellow" stroke="blue" stroke-width="5" />
<path d="M600,350 l 50,-25
a25,25 -30 0,1 50,-25 l 50,-25
a25,50 -30 0,1 50,-25 l 50,-25
a25,75 -30 0,1 50,-25 l 50,-25
a25,100 -30 0,1 50,-25 l 50,-25"
fill="none" stroke="red" stroke-width="5" />
<g font-family="Verdana" transform="translate(0,400)">
<defs>
<g id="baseEllipses" font-size="20" >
<ellipse cx="125" cy="125" rx="100" ry="50"
fill="none" stroke="#888888" stroke-width="2" />
<ellipse cx="225" cy="75" rx="100" ry="50"
fill="none" stroke="#888888" stroke-width="2" />
<text x="35" y="70">Arc start</text>
<text x="225" y="145">Arc end</text>
</g>
</defs>
<rect x="1" y="1" width="1198" height="523"
fill="none" stroke="blue" stroke-width="1" />
<g font-size="30" >
<g transform="translate(0,0)">
<use xlink:href="#baseEllipses"/>
</g>
<g transform="translate(400,0)">
<text x="50" y="210">large-arc-flag=0</text>
<text x="50" y="250">sweep-flag=0</text>
<use xlink:href="#baseEllipses"/>
<path d="M 125,75 a100,50 0 0,0 100,50"
fill="none" stroke="red" stroke-width="6" />
</g>
<g transform="translate(800,0)">
<text x="50" y="210">large-arc-flag=0</text>
<text x="50" y="250">sweep-flag=1</text>
<use xlink:href="#baseEllipses"/>
<path d="M 125,75 a100,50 0 0,1 100,50"
fill="none" stroke="red" stroke-width="6" />
</g>
<g transform="translate(400,250)">
<text x="50" y="210">large-arc-flag=1</text>
<text x="50" y="250">sweep-flag=0</text>
<use xlink:href="#baseEllipses"/>
<path d="M 125,75 a100,50 0 1,0 100,50"
fill="none" stroke="red" stroke-width="6" />
</g>
<g transform="translate(800,250)">
<text x="50" y="210">large-arc-flag=1</text>
<text x="50" y="250">sweep-flag=1</text>
<use xlink:href="#baseEllipses"/>
<path d="M 125,75 a100,50 0 1,1 100,50"
fill="none" stroke="red" stroke-width="6" />
</g>
</g>
</g>
</svg>
<canvas id="canvas" width="1000" height="500"></canvas>
</body>
</html>

View file

@ -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;
},

View file

@ -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.

View file

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

View file

@ -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,

View file

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

View file

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

View file

@ -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;
}
},

View file

@ -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

View file

@ -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 {

View file

@ -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) {

View file

@ -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;
}

View file

@ -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 */) {},