mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Large refactoring of mouse-handling code on View and CanvasView.
Added support for: - Better event bubbling - mouseenter / mouseleave events on view - Better handling of mousedrag / mousemove events on item and view - Support for #removeOn() call in item / view handlers Closes #845
This commit is contained in:
parent
ab68c5b272
commit
db2beba831
7 changed files with 245 additions and 234 deletions
|
@ -24,15 +24,15 @@ var Event = Base.extend(/** @lends Event# */{
|
||||||
this.event = event;
|
this.event = event;
|
||||||
},
|
},
|
||||||
|
|
||||||
isPrevented: false,
|
prevented: false,
|
||||||
isStopped: false,
|
stopped: false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels the event if it is cancelable, without stopping further
|
* Cancels the event if it is cancelable, without stopping further
|
||||||
* propagation of the event.
|
* propagation of the event.
|
||||||
*/
|
*/
|
||||||
preventDefault: function() {
|
preventDefault: function() {
|
||||||
this.isPrevented = true;
|
this.prevented = true;
|
||||||
this.event.preventDefault();
|
this.event.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ var Event = Base.extend(/** @lends Event# */{
|
||||||
* Prevents further propagation of the current event.
|
* Prevents further propagation of the current event.
|
||||||
*/
|
*/
|
||||||
stopPropagation: function() {
|
stopPropagation: function() {
|
||||||
this.isStopped = true;
|
this.stopped = true;
|
||||||
this.event.stopPropagation();
|
this.event.stopPropagation();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -2659,7 +2659,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
||||||
isDescendant: function(item) {
|
isDescendant: function(item) {
|
||||||
var parent = this;
|
var parent = this;
|
||||||
while (parent = parent._parent) {
|
while (parent = parent._parent) {
|
||||||
if (parent == item)
|
if (parent === item)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -351,7 +351,7 @@ var Segment = Base.extend(/** @lends Segment# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The curve location that describes this segment's position ont the path.
|
* The curve location that describes this segment's position on the path.
|
||||||
*
|
*
|
||||||
* @bean
|
* @bean
|
||||||
* @type CurveLocation
|
* @type CurveLocation
|
||||||
|
|
|
@ -709,6 +709,30 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
* SVG content
|
* SVG content
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
removeOn: function(type) {
|
||||||
|
var sets = this._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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
draw: function(ctx, matrix, pixelRatio) {
|
draw: function(ctx, matrix, pixelRatio) {
|
||||||
// Increase the _updateVersion before the draw-loop. After that, items
|
// Increase the _updateVersion before the draw-loop. After that, items
|
||||||
// that are visible will have their _updateVersion set to the new value.
|
// that are visible will have their _updateVersion set to the new value.
|
||||||
|
|
|
@ -322,33 +322,11 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
_fireEvent: function(type, event) {
|
_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)
|
return this.responds(type)
|
||||||
&& this.emit(type, new ToolEvent(this, type, event));
|
&& this.emit(type, new ToolEvent(this, type, event));
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleEvent: function(type, point, event) {
|
_handleEvent: function(type, event, point) {
|
||||||
// Update global reference to this scope.
|
// Update global reference to this scope.
|
||||||
paper = this._scope;
|
paper = this._scope;
|
||||||
// Now handle event callbacks
|
// Now handle event callbacks
|
||||||
|
|
|
@ -31,7 +31,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
||||||
* @name CanvasView#initialize
|
* @name CanvasView#initialize
|
||||||
* @param {Size} size the size of the canvas to be created
|
* @param {Size} size the size of the canvas to be created
|
||||||
*/
|
*/
|
||||||
initialize: function CanvasView(project, canvas) {
|
initialize: function(project, canvas) {
|
||||||
// Handle canvas argument
|
// Handle canvas argument
|
||||||
if (!(canvas instanceof HTMLCanvasElement)) {
|
if (!(canvas instanceof HTMLCanvasElement)) {
|
||||||
// See if the arguments describe the view size:
|
// See if the arguments describe the view size:
|
||||||
|
@ -43,8 +43,6 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
||||||
canvas = CanvasProvider.getCanvas(size);
|
canvas = CanvasProvider.getCanvas(size);
|
||||||
}
|
}
|
||||||
this._context = canvas.getContext('2d');
|
this._context = canvas.getContext('2d');
|
||||||
// Have Item count installed mouse events.
|
|
||||||
this._eventCounters = {};
|
|
||||||
this._pixelRatio = 1;
|
this._pixelRatio = 1;
|
||||||
/*#*/ if (__options.environment == 'browser') {
|
/*#*/ if (__options.environment == 'browser') {
|
||||||
if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) {
|
if (!/^off|false$/.test(PaperScope.getAttribute(canvas, 'hidpi'))) {
|
||||||
|
@ -144,150 +142,6 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
||||||
project._needsUpdate = false;
|
project._needsUpdate = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
new function() { // Item based mouse handling:
|
|
||||||
var downPoint,
|
|
||||||
lastPoint,
|
|
||||||
overPoint,
|
|
||||||
downItem,
|
|
||||||
lastItem,
|
|
||||||
overItem,
|
|
||||||
dragItem,
|
|
||||||
dblClick,
|
|
||||||
clickTime;
|
|
||||||
|
|
||||||
// Returns true if event was stopped, false otherwise, whether handler was
|
|
||||||
// called or not!
|
|
||||||
function callEvent(view, type, event, point, target, lastPoint) {
|
|
||||||
var item = target,
|
|
||||||
mouseEvent;
|
|
||||||
|
|
||||||
function call(obj, type) {
|
|
||||||
if (obj.responds(type)) {
|
|
||||||
// Only produce the event object if we really need it, and then
|
|
||||||
// reuse it if we're bubbling.
|
|
||||||
if (!mouseEvent) {
|
|
||||||
mouseEvent = new MouseEvent(type, event, point, target,
|
|
||||||
// Calculate delta if lastPoint was passed
|
|
||||||
lastPoint ? point.subtract(lastPoint) : null);
|
|
||||||
}
|
|
||||||
if (obj.emit(type, mouseEvent) && mouseEvent.isStopped) {
|
|
||||||
// Call preventDefault() on native event if mouse event was
|
|
||||||
// handled here.
|
|
||||||
event.preventDefault();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (type === 'doubleclick') {
|
|
||||||
// If obj doesn't respond to doubleclick, fall back to click:
|
|
||||||
return call(obj, 'click');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bubble up the parents and call this event until we're told to stop.
|
|
||||||
while (item) {
|
|
||||||
if (call(item, type))
|
|
||||||
return true;
|
|
||||||
item = item.getParent();
|
|
||||||
}
|
|
||||||
// Also call event handler on view, if installed.
|
|
||||||
if (call(view, type))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return /** @lends CanvasView# */{
|
|
||||||
/**
|
|
||||||
* Returns true if event was stopped, false otherwise, whether handler
|
|
||||||
* was called or not!
|
|
||||||
*/
|
|
||||||
_handleEvent: function(type, point, event) {
|
|
||||||
// Drop out if we don't have any event handlers for this type
|
|
||||||
if (!this._eventCounters[type])
|
|
||||||
return;
|
|
||||||
// Run the hit-test first
|
|
||||||
var project = this._project,
|
|
||||||
hit = project.hitTest(point, {
|
|
||||||
tolerance: 0,
|
|
||||||
fill: true,
|
|
||||||
stroke: true
|
|
||||||
}),
|
|
||||||
item = hit && hit.item,
|
|
||||||
stopped = false;
|
|
||||||
// Now handle the mouse events
|
|
||||||
switch (type) {
|
|
||||||
case 'mousedown':
|
|
||||||
stopped = callEvent(this, type, event, point, item);
|
|
||||||
// See if we're clicking again on the same item, within the
|
|
||||||
// double-click time. Firefox uses 300ms as the max time
|
|
||||||
// difference:
|
|
||||||
dblClick = lastItem == item && (Date.now() - clickTime < 300);
|
|
||||||
downItem = lastItem = item;
|
|
||||||
downPoint = lastPoint = overPoint = point;
|
|
||||||
// Only start dragging if none of the mosedown events have
|
|
||||||
// stopped propagation.
|
|
||||||
dragItem = !stopped && item;
|
|
||||||
// Find the first item pu the chain that responds to drag.
|
|
||||||
// NOTE: Drag event don't bubble
|
|
||||||
while (dragItem && !dragItem.responds('mousedrag'))
|
|
||||||
dragItem = dragItem._parent;
|
|
||||||
break;
|
|
||||||
case 'mouseup':
|
|
||||||
// stopping mousup events does not prevent mousedrag / mousemove
|
|
||||||
// hanlding here, but it does click / doubleclick
|
|
||||||
stopped = callEvent(this, type, event, point, item, downPoint);
|
|
||||||
if (dragItem) {
|
|
||||||
// If the point has changed since the last mousedrag event,
|
|
||||||
// send another one
|
|
||||||
if (lastPoint && !lastPoint.equals(point))
|
|
||||||
callEvent(this, 'mousedrag', event, point, dragItem,
|
|
||||||
lastPoint);
|
|
||||||
// If we end up over another item, send it a mousemove event
|
|
||||||
// now. Use point as overPoint, so delta is (0, 0) since
|
|
||||||
// this will be the first mousemove event for this item.
|
|
||||||
if (item !== dragItem) {
|
|
||||||
overPoint = point;
|
|
||||||
callEvent(this, 'mousemove', event, point, item,
|
|
||||||
overPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!stopped && item && item === downItem) {
|
|
||||||
clickTime = Date.now();
|
|
||||||
callEvent(this, dblClick ? 'doubleclick' : 'click', event,
|
|
||||||
downPoint, item);
|
|
||||||
dblClick = false;
|
|
||||||
}
|
|
||||||
downItem = dragItem = null;
|
|
||||||
break;
|
|
||||||
case 'mousemove':
|
|
||||||
// Allow both mousedrag and mousemove events to stop mousemove
|
|
||||||
// events from reaching tools.
|
|
||||||
if (dragItem)
|
|
||||||
stopped = callEvent(this, 'mousedrag', event, point,
|
|
||||||
dragItem, lastPoint);
|
|
||||||
// TODO: Consider implementing this again? "If we have a
|
|
||||||
// mousedrag event, do not send mousemove events to any
|
|
||||||
// item while we're dragging."
|
|
||||||
// For now, we let other items receive mousemove events even
|
|
||||||
// during a drag event.
|
|
||||||
// If we change the overItem, reset overPoint to point so
|
|
||||||
// delta is (0, 0)
|
|
||||||
if (!stopped) {
|
|
||||||
if (item !== overItem)
|
|
||||||
overPoint = point;
|
|
||||||
stopped = callEvent(this, type, event, point, item,
|
|
||||||
overPoint);
|
|
||||||
}
|
|
||||||
lastPoint = overPoint = point;
|
|
||||||
if (item !== overItem) {
|
|
||||||
callEvent(this, 'mouseleave', event, point, overItem);
|
|
||||||
overItem = item;
|
|
||||||
callEvent(this, 'mouseenter', event, point, item);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return stopped;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*#*/ if (__options.environment == 'node') {
|
/*#*/ if (__options.environment == 'node') {
|
||||||
|
|
271
src/view/View.js
271
src/view/View.js
|
@ -119,6 +119,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
||||||
// Items that need the onFrame handler called on them
|
// Items that need the onFrame handler called on them
|
||||||
this._frameItems = {};
|
this._frameItems = {};
|
||||||
this._frameItemCount = 0;
|
this._frameItemCount = 0;
|
||||||
|
// Count the installed events, see _installEvent() / _uninstallEvent().
|
||||||
|
this._eventCounters = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,7 +152,8 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
_events: Base.each(['onResize', 'onMouseDown', 'onMouseUp', 'onMouseMove'],
|
_events: Base.each(['onResize', 'onMouseDown', 'onMouseUp', 'onMouseMove',
|
||||||
|
'onMouseDrag', 'onMouseEnter', 'onMouseLeave'],
|
||||||
function(name) {
|
function(name) {
|
||||||
this[name] = {
|
this[name] = {
|
||||||
install: function(type) {
|
install: function(type) {
|
||||||
|
@ -681,16 +684,23 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
||||||
},
|
},
|
||||||
new function() { // Injection scope for mouse events on the browser
|
new function() { // Injection scope for mouse events on the browser
|
||||||
/*#*/ if (__options.environment == 'browser') {
|
/*#*/ if (__options.environment == 'browser') {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native event handling, coordinate conversion, focus handling and
|
||||||
|
* delegation to view and tool objects.
|
||||||
|
*/
|
||||||
|
|
||||||
var tool,
|
var tool,
|
||||||
prevFocus,
|
prevFocus,
|
||||||
tempFocus,
|
tempFocus,
|
||||||
dragging = false;
|
mouseDown = false;
|
||||||
|
|
||||||
function getView(event) {
|
function getView(event) {
|
||||||
// Get the view from the current event target.
|
// Get the view from the current event target.
|
||||||
var target = DomEvent.getTarget(event);
|
var target = DomEvent.getTarget(event);
|
||||||
// Some node do not have the getAttribute method, e.g. SVG nodes.
|
// Some node do not have the getAttribute method, e.g. SVG nodes.
|
||||||
return target.getAttribute && View._viewsById[target.getAttribute('id')];
|
return target.getAttribute && View._viewsById[
|
||||||
|
target.getAttribute('id')];
|
||||||
}
|
}
|
||||||
|
|
||||||
function viewToProject(view, event) {
|
function viewToProject(view, event) {
|
||||||
|
@ -710,16 +720,33 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseMove(view, point, event) {
|
function handleEvent(type, view, event, point) {
|
||||||
view._handleEvent('mousemove', point, event);
|
var eventType = type === 'mousemove' && mouseDown ? 'mousedrag' : type,
|
||||||
var tool = view._scope.tool;
|
project = paper.project,
|
||||||
if (tool) {
|
tool = view._scope.tool;
|
||||||
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
|
||||||
tool._handleEvent(dragging && tool.responds('mousedrag')
|
function handle(obj) {
|
||||||
? 'mousedrag' : 'mousemove', point, event);
|
obj._handleEvent(eventType, event, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!point)
|
||||||
|
point = viewToProject(view, event);
|
||||||
|
if (project)
|
||||||
|
project.removeOn(eventType);
|
||||||
|
// Always first call the view's mouse handlers, as required by
|
||||||
|
// CanvasView, and then handle the active tool, if any.
|
||||||
|
// No need to call view if it doesn't have event handlers for this type.
|
||||||
|
if (view._eventCounters[type])
|
||||||
|
handle(view);
|
||||||
|
if (tool)
|
||||||
|
handle(tool);
|
||||||
|
// In the end we always call update(), which only updates the view if
|
||||||
|
// anything has changed in the above calls.
|
||||||
view.update();
|
view.update();
|
||||||
return tool;
|
}
|
||||||
|
|
||||||
|
function handleMouseMove(view, event, point) {
|
||||||
|
handleEvent('mousemove', view, event, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch handling inspired by Hammer.js
|
// Touch handling inspired by Hammer.js
|
||||||
|
@ -747,9 +774,9 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
|
|
||||||
var viewEvents = {
|
var viewEvents = {
|
||||||
'selectstart dragstart': function(event) {
|
'selectstart dragstart': function(event) {
|
||||||
// Only stop this even if we're dragging already, since otherwise no
|
// Only stop this even if we're mouseDown already, since otherwise
|
||||||
// text whatsoever can be selected on the page.
|
// no text whatsoever can be selected on the page.
|
||||||
if (dragging)
|
if (mouseDown)
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -766,10 +793,12 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
// TODO: Remove again after Dec 2016 once it is fixed in Chrome.
|
// TODO: Remove again after Dec 2016 once it is fixed in Chrome.
|
||||||
var offset = DomEvent.getOffset(event, view._element),
|
var offset = DomEvent.getOffset(event, view._element),
|
||||||
x = offset.x,
|
x = offset.x,
|
||||||
abs = Math.abs(x),
|
abs = Math.abs,
|
||||||
max = 1 << 25;
|
ax = abs(x),
|
||||||
offset.x = abs - max < abs ? (abs - max) * (x < 0 ? -1 : 1) : x;
|
max = 1 << 25,
|
||||||
handleMouseMove(view, view.viewToProject(offset), event);
|
diff = ax - max;
|
||||||
|
offset.x = abs(diff) < ax ? diff * (x < 0 ? -1 : 1) : x;
|
||||||
|
handleMouseMove(view, event, view.viewToProject(offset));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -783,22 +812,14 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
viewEvents[mousedown] = function(event) {
|
viewEvents[mousedown] = function(event) {
|
||||||
// Get the view from the event, and store a reference to the view that
|
// Get the view from the event, and store a reference to the view that
|
||||||
// should receive keyboard input.
|
// should receive keyboard input.
|
||||||
var view = View._focused = getView(event),
|
var view = View._focused = getView(event);
|
||||||
point = viewToProject(view, event);
|
mouseDown = true;
|
||||||
dragging = true;
|
handleEvent('mousedown', view, event);
|
||||||
// Always first call the view's mouse handlers, as required by
|
|
||||||
// CanvasView, and then handle the active tool, if any.
|
|
||||||
view._handleEvent('mousedown', point, event);
|
|
||||||
if (tool = view._scope.tool)
|
|
||||||
tool._handleEvent('mousedown', point, event);
|
|
||||||
// In the end we always call update(), which only updates the view if
|
|
||||||
// anything has changed in the above calls.
|
|
||||||
view.update();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
docEvents[mousemove] = function(event) {
|
docEvents[mousemove] = function(event) {
|
||||||
var view = View._focused;
|
var view = View._focused;
|
||||||
if (!dragging) {
|
if (!mouseDown) {
|
||||||
// See if we can get the view from the current event target, and
|
// See if we can get the view from the current event target, and
|
||||||
// handle the mouse move over it.
|
// handle the mouse move over it.
|
||||||
var target = getView(event);
|
var target = getView(event);
|
||||||
|
@ -808,7 +829,7 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
// If we switch view, fire one last mousemove in the old view,
|
// If we switch view, fire one last mousemove in the old view,
|
||||||
// to give items the change to receive a mouseleave, etc.
|
// to give items the change to receive a mouseleave, etc.
|
||||||
if (view !== target)
|
if (view !== target)
|
||||||
handleMouseMove(view, viewToProject(view, event), event);
|
handleMouseMove(view, event);
|
||||||
prevFocus = view;
|
prevFocus = view;
|
||||||
view = View._focused = tempFocus = target;
|
view = View._focused = tempFocus = target;
|
||||||
} else if (tempFocus && tempFocus === view) {
|
} else if (tempFocus && tempFocus === view) {
|
||||||
|
@ -817,23 +838,15 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
updateFocus();
|
updateFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (view) {
|
if (view)
|
||||||
var point = viewToProject(view, event);
|
handleMouseMove(view, event);
|
||||||
if (dragging || view.getBounds().contains(point))
|
|
||||||
tool = handleMouseMove(view, point, event);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
docEvents[mouseup] = function(event) {
|
docEvents[mouseup] = function(event) {
|
||||||
var view = View._focused;
|
var view = View._focused;
|
||||||
if (!view || !dragging)
|
if (view && mouseDown)
|
||||||
return;
|
handleEvent('mouseup', view, event);
|
||||||
var point = viewToProject(view, event);
|
mouseDown = false;
|
||||||
dragging = false;
|
|
||||||
view._handleEvent('mouseup', point, event);
|
|
||||||
if (tool)
|
|
||||||
tool._handleEvent('mouseup', point, event);
|
|
||||||
view.update();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DomEvent.add(document, docEvents);
|
DomEvent.add(document, docEvents);
|
||||||
|
@ -842,9 +855,84 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
load: updateFocus
|
load: updateFocus
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flags defining which native events are required by which Paper events
|
/**
|
||||||
// as required for counting amount of necessary natives events.
|
* Higher level event handling, hit-testing, and emitting of normal mouse
|
||||||
// The mapping is native -> virtual
|
* events along with "virtual" events such as mouseenter, mouseleave,
|
||||||
|
* mousedrag, click, doubleclick, on both the hit-test item and the view,
|
||||||
|
* with support for bubbling (event-propagation).
|
||||||
|
*/
|
||||||
|
|
||||||
|
var downPoint,
|
||||||
|
lastPoint,
|
||||||
|
downItem,
|
||||||
|
overItem,
|
||||||
|
dragItem,
|
||||||
|
clickItem,
|
||||||
|
clickTime,
|
||||||
|
dblClick,
|
||||||
|
overView,
|
||||||
|
// Event fallbacks for "virutal" events, e.g. if an item doesn't respond
|
||||||
|
// to doubleclick, fall back to click:
|
||||||
|
fallbacks = {
|
||||||
|
doubleclick: 'click',
|
||||||
|
mousedrag: 'mousemove'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns true if event was stopped, false otherwise, whether handler was
|
||||||
|
// called or not!
|
||||||
|
function emitEvent(obj, type, event, point, prevPoint, stopItem) {
|
||||||
|
var target = obj,
|
||||||
|
mouseEvent;
|
||||||
|
|
||||||
|
function emit(obj, type) {
|
||||||
|
if (obj.responds(type)) {
|
||||||
|
// Only produce the event object if we really need it, and then
|
||||||
|
// reuse it if we're bubbling.
|
||||||
|
if (!mouseEvent) {
|
||||||
|
mouseEvent = new MouseEvent(
|
||||||
|
type, event, point, target, mouseDown,
|
||||||
|
// Calculate delta if prevPoint was passed
|
||||||
|
prevPoint ? point.subtract(prevPoint) : null);
|
||||||
|
}
|
||||||
|
// Bail out if propagation is stopped
|
||||||
|
if (obj.emit(type, mouseEvent) && mouseEvent.stopped)
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
var fallback = fallbacks[type];
|
||||||
|
if (fallback)
|
||||||
|
return emit(obj, fallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bubble up the parents and emit this event until we're told to stop.
|
||||||
|
while (obj && obj !== stopItem) {
|
||||||
|
if (emit(obj, type))
|
||||||
|
return true;
|
||||||
|
obj = obj._parent;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitEvents(view, item, type, event, point, prevPoint) {
|
||||||
|
// First handle the drag-item and its parents, through bubbling.
|
||||||
|
return (dragItem && emitEvent(dragItem, type, event, point,
|
||||||
|
prevPoint)
|
||||||
|
// Next handle the hit-test item, if it's different from the drag
|
||||||
|
// item and not a descendant of it (in which case it would already
|
||||||
|
// have received an event in the call above). Use fallbacks to
|
||||||
|
// translate mousedrag to mousemove, since drag is handled above.
|
||||||
|
|| item && item !== dragItem && !item.isDescendant(dragItem)
|
||||||
|
&& emitEvent(item, fallbacks[type] || type, event, point,
|
||||||
|
prevPoint, dragItem)
|
||||||
|
// Lastly handle the move / drag on the view, if we're still here.
|
||||||
|
|| emitEvent(view, type, event, point, prevPoint));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags defining which native events are required by which Paper events
|
||||||
|
* as required for counting amount of necessary natives events.
|
||||||
|
* The mapping is native -> virtual
|
||||||
|
*/
|
||||||
var mouseFlags = {
|
var mouseFlags = {
|
||||||
mousedown: {
|
mousedown: {
|
||||||
mousedown: 1,
|
mousedown: 1,
|
||||||
|
@ -869,18 +957,87 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
return {
|
return {
|
||||||
_viewEvents: viewEvents,
|
_viewEvents: viewEvents,
|
||||||
|
|
||||||
// To be defined in subclasses
|
/**
|
||||||
_handleEvent: function(/* type, point, event */) {},
|
* Returns true if event was stopped, false otherwise, whether handler
|
||||||
|
* was called or not!
|
||||||
|
*/
|
||||||
|
_handleEvent: function(type, event, point) {
|
||||||
|
// Run the hit-test first
|
||||||
|
var hit = this._project.hitTest(point, {
|
||||||
|
tolerance: 0,
|
||||||
|
fill: true,
|
||||||
|
stroke: true
|
||||||
|
}),
|
||||||
|
item = hit && hit.item,
|
||||||
|
inView = this.getBounds().contains(point),
|
||||||
|
stopped = false,
|
||||||
|
mouse = {};
|
||||||
|
// Create a simple lookup object to quickly check for different
|
||||||
|
// mouse event types.
|
||||||
|
mouse[type.substr(5)] = true;
|
||||||
|
// Handle mousemove first, even if this is not actually a mousemove
|
||||||
|
// event but the mouse has moved since the last event, but do not
|
||||||
|
// allow it to stop the other events in that case.
|
||||||
|
var nativeMove = mouse.move || mouse.drag;
|
||||||
|
moveType = nativeMove && type
|
||||||
|
|| lastPoint && !lastPoint.equals(point) && 'mousemove';
|
||||||
|
if (moveType) {
|
||||||
|
// Handle mouseenter / leave between items, as well as views.
|
||||||
|
if (item !== overItem) {
|
||||||
|
if (overItem)
|
||||||
|
emitEvent(overItem, 'mouseleave', event, point);
|
||||||
|
if (item)
|
||||||
|
emitEvent(item, 'mouseenter', event, point);
|
||||||
|
}
|
||||||
|
overItem = item;
|
||||||
|
if (overView && !overView.getBounds().contains(point)) {
|
||||||
|
emitEvent(overView, 'mouseleave', event, point);
|
||||||
|
overView = null;
|
||||||
|
}
|
||||||
|
if (this !== overView && inView) {
|
||||||
|
emitEvent(this, 'mouseenter', event, point);
|
||||||
|
overView = this;
|
||||||
|
}
|
||||||
|
if (inView || mouse.drag)
|
||||||
|
stopped = emitEvents(this, item, moveType, event, point,
|
||||||
|
lastPoint);
|
||||||
|
}
|
||||||
|
if (!nativeMove) {
|
||||||
|
// Now handle mousedown / mouseup
|
||||||
|
stopped = emitEvents(this, item, type, event, point, downPoint);
|
||||||
|
if (mouse.down) {
|
||||||
|
// See if we're clicking again on the same item, within the
|
||||||
|
// double-click time. Firefox uses 300ms as the max time
|
||||||
|
// difference:
|
||||||
|
dblClick = clickItem === item
|
||||||
|
&& (Date.now() - clickTime < 300);
|
||||||
|
downItem = clickItem = item;
|
||||||
|
downPoint = lastPoint = point;
|
||||||
|
// Only start dragging if the mousedown event has not
|
||||||
|
// stopped propagation.
|
||||||
|
dragItem = !stopped && item;
|
||||||
|
} else if (mouse.up) {
|
||||||
|
// Emulate click / doubleclick, but only on item, not view
|
||||||
|
if (!stopped && item && item === downItem) {
|
||||||
|
clickTime = Date.now();
|
||||||
|
emitEvent(item, dblClick ? 'doubleclick' : 'click',
|
||||||
|
event, point, downPoint);
|
||||||
|
dblClick = false;
|
||||||
|
}
|
||||||
|
downItem = dragItem = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastPoint = point;
|
||||||
|
return stopped;
|
||||||
|
},
|
||||||
|
|
||||||
_installEvent: function(type) {
|
_installEvent: function(type) {
|
||||||
// If the view requires counting of installed mouse events,
|
// If the view requires counting of installed mouse events,
|
||||||
// increase the counters now according to mouseFlags
|
// increase the counters now according to mouseFlags
|
||||||
var counters = this._eventCounters;
|
var counters = this._eventCounters;
|
||||||
if (counters) {
|
for (var key in mouseFlags) {
|
||||||
for (var key in mouseFlags) {
|
counters[key] = (counters[key] || 0)
|
||||||
counters[key] = (counters[key] || 0)
|
+ (mouseFlags[key][type] || 0);
|
||||||
+ (mouseFlags[key][type] || 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -888,10 +1045,8 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
// If the view requires counting of installed mouse events,
|
// If the view requires counting of installed mouse events,
|
||||||
// decrease the counters now according to mouseFlags
|
// decrease the counters now according to mouseFlags
|
||||||
var counters = this._eventCounters;
|
var counters = this._eventCounters;
|
||||||
if (counters) {
|
for (var key in mouseFlags)
|
||||||
for (var key in mouseFlags)
|
counters[key] -= mouseFlags[key][type] || 0;
|
||||||
counters[key] -= mouseFlags[key][type] || 0;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
|
|
Loading…
Reference in a new issue