mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-22 07:19:57 -05:00
476 lines
16 KiB
JavaScript
476 lines
16 KiB
JavaScript
/*
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
|
* http://paperjs.org/
|
|
*
|
|
* Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey
|
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
|
*
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
*
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/**
|
|
* @name Tool
|
|
*
|
|
* @class The Tool object refers to a script that the user can interact with
|
|
* by using the mouse and keyboard and can be accessed through the global
|
|
* {@code tool} variable. All its properties are also available in the paper
|
|
* scope.
|
|
*
|
|
* The global {@code tool} variable only exists in scripts that contain mouse
|
|
* handler functions ({@link #onMouseMove}, {@link #onMouseDown},
|
|
* {@link #onMouseDrag}, {@link #onMouseUp}) or a keyboard handler
|
|
* function ({@link #onKeyDown}, {@link #onKeyUp}).
|
|
*
|
|
* @classexample
|
|
* var path;
|
|
*
|
|
* // Only execute onMouseDrag when the mouse
|
|
* // has moved at least 10 points:
|
|
* tool.distanceThreshold = 10;
|
|
*
|
|
* function onMouseDown(event) {
|
|
* // Create a new path every time the mouse is clicked
|
|
* path = new Path();
|
|
* path.add(event.point);
|
|
* path.strokeColor = 'black';
|
|
* }
|
|
*
|
|
* function onMouseDrag(event) {
|
|
* // Add a point to the path every time the mouse is dragged
|
|
* path.add(event.point);
|
|
* }
|
|
*/
|
|
var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
|
_class: 'Tool',
|
|
_list: 'tools',
|
|
_reference: 'tool',
|
|
_events: [ 'onActivate', 'onDeactivate', 'onEditOptions',
|
|
'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
|
|
'onKeyDown', 'onKeyUp' ],
|
|
|
|
// DOCS: rewrite Tool constructor explanation
|
|
initialize: function Tool(props) {
|
|
PaperScopeItem.call(this);
|
|
this._firstMove = true;
|
|
this._count = 0;
|
|
this._downCount = 0;
|
|
this._set(props);
|
|
},
|
|
|
|
/**
|
|
* Activates this tool, meaning {@link PaperScope#tool} will
|
|
* point to it and it will be the one that recieves mouse events.
|
|
*
|
|
* @name Tool#activate
|
|
* @function
|
|
*/
|
|
|
|
/**
|
|
* Removes this tool from the {@link PaperScope#tools} list.
|
|
*
|
|
* @name Tool#remove
|
|
* @function
|
|
*/
|
|
|
|
/**
|
|
* The minimum distance the mouse has to drag before firing the onMouseDrag
|
|
* event, since the last onMouseDrag event.
|
|
*
|
|
* @type Number
|
|
* @bean
|
|
*/
|
|
getMinDistance: function() {
|
|
return this._minDistance;
|
|
},
|
|
|
|
setMinDistance: function(minDistance) {
|
|
this._minDistance = minDistance;
|
|
if (this._minDistance != null && this._maxDistance != null
|
|
&& this._minDistance > this._maxDistance) {
|
|
this._maxDistance = this._minDistance;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* The maximum distance the mouse has to drag before firing the onMouseDrag
|
|
* event, since the last onMouseDrag event.
|
|
*
|
|
* @type Number
|
|
* @bean
|
|
*/
|
|
getMaxDistance: function() {
|
|
return this._maxDistance;
|
|
},
|
|
|
|
setMaxDistance: function(maxDistance) {
|
|
this._maxDistance = maxDistance;
|
|
if (this._minDistance != null && this._maxDistance != null
|
|
&& this._maxDistance < this._minDistance) {
|
|
this._minDistance = maxDistance;
|
|
}
|
|
},
|
|
|
|
// DOCS: document Tool#fixedDistance
|
|
/**
|
|
* @type Number
|
|
* @bean
|
|
*/
|
|
getFixedDistance: function() {
|
|
return this._minDistance == this._maxDistance
|
|
? this._minDistance : null;
|
|
},
|
|
|
|
setFixedDistance: function(distance) {
|
|
this._minDistance = distance;
|
|
this._maxDistance = distance;
|
|
},
|
|
|
|
/**
|
|
* {@grouptitle Mouse Event Handlers}
|
|
*
|
|
* The function to be called when the mouse button is pushed down. The
|
|
* function receives a {@link ToolEvent} object which contains information
|
|
* about the mouse event.
|
|
*
|
|
* @name Tool#onMouseDown
|
|
* @property
|
|
* @type Function
|
|
*
|
|
* @example {@paperscript}
|
|
* // Creating circle shaped paths where the user presses the mouse button:
|
|
* function onMouseDown(event) {
|
|
* // Create a new circle shaped path with a radius of 10
|
|
* // at the position of the mouse (event.point):
|
|
* var path = new Path.Circle({
|
|
* center: event.point,
|
|
* radius: 10,
|
|
* fillColor: 'black'
|
|
* });
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* The function to be called when the mouse position changes while the mouse
|
|
* is being dragged. The function receives a {@link ToolEvent} object which
|
|
* contains information about the mouse event.
|
|
*
|
|
* @name Tool#onMouseDrag
|
|
* @property
|
|
* @type Function
|
|
*
|
|
* @example {@paperscript}
|
|
* // Draw a line by adding a segment to a path on every mouse drag event:
|
|
*
|
|
* // Create an empty path:
|
|
* var path = new Path({
|
|
* strokeColor: 'black'
|
|
* });
|
|
*
|
|
* function onMouseDrag(event) {
|
|
* // Add a segment to the path at the position of the mouse:
|
|
* path.add(event.point);
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* The function to be called the mouse moves within the project view. The
|
|
* function receives a {@link ToolEvent} object which contains information
|
|
* about the mouse event.
|
|
*
|
|
* @name Tool#onMouseMove
|
|
* @property
|
|
* @type Function
|
|
*
|
|
* @example {@paperscript}
|
|
* // Moving a path to the position of the mouse:
|
|
*
|
|
* // Create a circle shaped path with a radius of 10 at {x: 0, y: 0}:
|
|
* var path = new Path.Circle({
|
|
* center: [0, 0],
|
|
* radius: 10,
|
|
* fillColor: 'black'
|
|
* });
|
|
*
|
|
* function onMouseMove(event) {
|
|
* // Whenever the user moves the mouse, move the path
|
|
* // to that position:
|
|
* path.position = event.point;
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* The function to be called when the mouse button is released. The function
|
|
* receives a {@link ToolEvent} object which contains information about the
|
|
* mouse event.
|
|
*
|
|
* @name Tool#onMouseUp
|
|
* @property
|
|
* @type Function
|
|
*
|
|
* @example {@paperscript}
|
|
* // Creating circle shaped paths where the user releases the mouse:
|
|
* function onMouseUp(event) {
|
|
* // Create a new circle shaped path with a radius of 10
|
|
* // at the position of the mouse (event.point):
|
|
* var path = new Path.Circle({
|
|
* center: event.point,
|
|
* radius: 10,
|
|
* fillColor: 'black'
|
|
* });
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* {@grouptitle Keyboard Event Handlers}
|
|
*
|
|
* The function to be called when the user presses a key on the keyboard.
|
|
* The function receives a {@link KeyEvent} object which contains
|
|
* information about the keyboard event.
|
|
* If the function returns {@code false}, the keyboard event will be
|
|
* prevented from bubbling up. This can be used for example to stop the
|
|
* window from scrolling, when you need the user to interact with arrow
|
|
* keys.
|
|
*
|
|
* @name Tool#onKeyDown
|
|
* @property
|
|
* @type Function
|
|
*
|
|
* @example {@paperscript}
|
|
* // Scaling a path whenever the user presses the space bar:
|
|
*
|
|
* // Create a circle shaped path:
|
|
* var path = new Path.Circle({
|
|
* center: new Point(50, 50),
|
|
* radius: 30,
|
|
* fillColor: 'red'
|
|
* });
|
|
*
|
|
* function onKeyDown(event) {
|
|
* if (event.key == 'space') {
|
|
* // Scale the path by 110%:
|
|
* path.scale(1.1);
|
|
*
|
|
* // Prevent the key event from bubbling
|
|
* return false;
|
|
* }
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* The function to be called when the user releases a key on the keyboard.
|
|
* The function receives a {@link KeyEvent} object which contains
|
|
* information about the keyboard event.
|
|
* If the function returns {@code false}, the keyboard event will be
|
|
* prevented from bubbling up. This can be used for example to stop the
|
|
* window from scrolling, when you need the user to interact with arrow
|
|
* keys.
|
|
*
|
|
* @name Tool#onKeyUp
|
|
* @property
|
|
* @type Function
|
|
*
|
|
* @example
|
|
* function onKeyUp(event) {
|
|
* if (event.key == 'space') {
|
|
* console.log('The spacebar was released!');
|
|
* }
|
|
* }
|
|
*/
|
|
|
|
_updateEvent: function(type, point, minDistance, maxDistance, start,
|
|
needsChange, matchMaxDistance) {
|
|
if (!start) {
|
|
if (minDistance != null || maxDistance != null) {
|
|
var minDist = minDistance != null ? minDistance : 0,
|
|
vector = point.subtract(this._point),
|
|
distance = vector.getLength();
|
|
if (distance < minDist)
|
|
return false;
|
|
// Produce a new point on the way to point if point is further
|
|
// away than maxDistance
|
|
var maxDist = maxDistance != null ? maxDistance : 0;
|
|
if (maxDist != 0) {
|
|
if (distance > maxDist) {
|
|
point = this._point.add(vector.normalize(maxDist));
|
|
} else if (matchMaxDistance) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (needsChange && point.equals(this._point))
|
|
return false;
|
|
}
|
|
// Make sure mousemove events have lastPoint set even for the first move
|
|
// so event.delta is always defined for them.
|
|
// TODO: Decide whether mousedown also should always have delta set.
|
|
this._lastPoint = start && type == 'mousemove' ? point : this._point;
|
|
this._point = point;
|
|
switch (type) {
|
|
case 'mousedown':
|
|
this._lastPoint = this._downPoint;
|
|
this._downPoint = this._point;
|
|
this._downCount++;
|
|
break;
|
|
case 'mouseup':
|
|
// Mouse up events return the down point for last point, so delta is
|
|
// spanning over the whole drag.
|
|
this._lastPoint = this._downPoint;
|
|
break;
|
|
}
|
|
this._count = start ? 0 : this._count + 1;
|
|
return true;
|
|
},
|
|
|
|
_fireEvent: function(type, event) {
|
|
// Handle items marked in removeOn*() calls first,.
|
|
var sets = paper.project._removeSets;
|
|
if (sets) {
|
|
// Always clear the drag set on mouseup
|
|
if (type === 'mouseup')
|
|
sets.mousedrag = null;
|
|
var set = sets[type];
|
|
if (set) {
|
|
for (var id in set) {
|
|
var item = set[id];
|
|
// If we remove this item, we also need to erase it from all
|
|
// other sets.
|
|
for (var key in sets) {
|
|
var other = sets[key];
|
|
if (other && other != set)
|
|
delete other[item._id];
|
|
}
|
|
item.remove();
|
|
}
|
|
sets[type] = null;
|
|
}
|
|
}
|
|
return this.responds(type)
|
|
&& this.emit(type, new ToolEvent(this, type, event));
|
|
},
|
|
|
|
_handleEvent: function(type, point, event) {
|
|
// Update global reference to this scope.
|
|
paper = this._scope;
|
|
// Now handle event callbacks
|
|
var called = false;
|
|
switch (type) {
|
|
case 'mousedown':
|
|
this._updateEvent(type, point, null, null, true, false, false);
|
|
called = this._fireEvent(type, event);
|
|
break;
|
|
case 'mousedrag':
|
|
// In order for idleInterval drag events to work, we need to not
|
|
// check the first call for a change of position. Subsequent calls
|
|
// required by min/maxDistance functionality will require it,
|
|
// otherwise this might loop endlessly.
|
|
var needsChange = false,
|
|
// If the mouse is moving faster than maxDistance, do not produce
|
|
// events for what is left after the first event is generated in
|
|
// case it is shorter than maxDistance, as this would produce weird
|
|
// results. matchMaxDistance controls this.
|
|
matchMaxDistance = false;
|
|
while (this._updateEvent(type, point, this.minDistance,
|
|
this.maxDistance, false, needsChange, matchMaxDistance)) {
|
|
called = this._fireEvent(type, event) || called;
|
|
needsChange = true;
|
|
matchMaxDistance = true;
|
|
}
|
|
break;
|
|
case 'mouseup':
|
|
// If the last mouse drag happened in a different place, call mouse
|
|
// drag first, then mouse up.
|
|
if (!point.equals(this._point)
|
|
&& this._updateEvent('mousedrag', point, this.minDistance,
|
|
this.maxDistance, false, false, false)) {
|
|
called = this._fireEvent('mousedrag', event);
|
|
}
|
|
this._updateEvent(type, point, null, this.maxDistance, false,
|
|
false, false);
|
|
called = this._fireEvent(type, event) || called;
|
|
// Start with new values for 'mousemove'
|
|
this._updateEvent(type, point, null, null, true, false, false);
|
|
this._firstMove = true;
|
|
break;
|
|
case 'mousemove':
|
|
while (this._updateEvent(type, point, this.minDistance,
|
|
this.maxDistance, this._firstMove, true, false)) {
|
|
called = this._fireEvent(type, event) || called;
|
|
this._firstMove = false;
|
|
}
|
|
break;
|
|
}
|
|
// Prevent default if mouse event was handled.
|
|
if (called)
|
|
event.preventDefault();
|
|
return called;
|
|
}
|
|
/**
|
|
* {@grouptitle Event Handling}
|
|
*
|
|
* Attach an event handler to the tool.
|
|
*
|
|
* @name Tool#attach
|
|
* @alias Tool#on
|
|
* @function
|
|
* @param {String('mousedown', 'mouseup', 'mousedrag', 'mousemove',
|
|
* 'keydown', 'keyup')} type the event type
|
|
* @param {Function} function The function to be called when the event
|
|
* occurs
|
|
*/
|
|
/**
|
|
* Attach one or more event handlers to the tool.
|
|
*
|
|
* @name Tool#attach
|
|
* @alias Tool#on
|
|
* @function
|
|
* @param {Object} param an object literal containing one or more of the
|
|
* following properties: {@code mousedown, mouseup, mousedrag, mousemove,
|
|
* keydown, keyup}.
|
|
*/
|
|
|
|
/**
|
|
* Detach an event handler from the tool.
|
|
*
|
|
* @name Tool#detach
|
|
* @alias Tool#off
|
|
* @function
|
|
* @param {String('mousedown', 'mouseup', 'mousedrag', 'mousemove',
|
|
* 'keydown', 'keyup')} type the event type
|
|
* @param {Function} function The function to be detached
|
|
*/
|
|
/**
|
|
* Detach one or more event handlers from the tool.
|
|
*
|
|
* @name Tool#detach
|
|
* @alias Tool#off
|
|
* @function
|
|
* @param {Object} param an object literal containing one or more of the
|
|
* following properties: {@code mousedown, mouseup, mousedrag, mousemove,
|
|
* keydown, keyup}
|
|
*/
|
|
|
|
/**
|
|
* Fire an event on the tool.
|
|
*
|
|
* @name Tool#fire
|
|
* @alias Tool#trigger
|
|
* @function
|
|
* @param {String('mousedown', 'mouseup', 'mousedrag', 'mousemove',
|
|
* 'keydown', 'keyup')} type the event type
|
|
* @param {Object} event an object literal containing properties describing
|
|
* the event.
|
|
*/
|
|
|
|
/**
|
|
* Check if the tool has one or more event handlers of the specified type.
|
|
*
|
|
* @name Tool#responds
|
|
* @function
|
|
* @param {String('mousedown', 'mouseup', 'mousedrag', 'mousemove',
|
|
* 'keydown', 'keyup')} type the event type
|
|
* @return {Boolean} {@true if the tool has one or more event handlers of
|
|
* the specified type}
|
|
*/
|
|
});
|