mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-03-15 01:09:53 -04:00
Completely rework event handling on view and tools.
Fixes multiple issues on iOS: - mousedown events were sometimes fired twice., - the presence of mousedown handlers broke scrolling. Closes #266.
This commit is contained in:
parent
0797202b22
commit
2cfa329fa6
6 changed files with 116 additions and 118 deletions
|
@ -86,8 +86,10 @@ var Callback = {
|
||||||
// When the handler function returns false, prevent the default
|
// When the handler function returns false, prevent the default
|
||||||
// behaviour and stop propagation of the event by calling stop()
|
// behaviour and stop propagation of the event by calling stop()
|
||||||
if (handlers[i].apply(that, args) === false
|
if (handlers[i].apply(that, args) === false
|
||||||
&& event && event.stop)
|
&& event && event.stop) {
|
||||||
event.stop();
|
event.stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// See PaperScript.handleException for an explanation of the following.
|
// See PaperScript.handleException for an explanation of the following.
|
||||||
|
@ -135,9 +137,7 @@ var Callback = {
|
||||||
types[type] = isString ? {} : entry;
|
types[type] = isString ? {} : entry;
|
||||||
// Create getters and setters for the property
|
// Create getters and setters for the property
|
||||||
// with the on*-name name:
|
// with the on*-name name:
|
||||||
// Use '__' as there are some _onMouse* functions
|
name = '_' + name;
|
||||||
// already, e.g.g on View.
|
|
||||||
name = '__' + name;
|
|
||||||
src['get' + part] = function() {
|
src['get' + part] = function() {
|
||||||
return this[name];
|
return this[name];
|
||||||
};
|
};
|
||||||
|
|
|
@ -48,26 +48,9 @@ var DomEvent = {
|
||||||
target || DomEvent.getTarget(event)));
|
target || DomEvent.getTarget(event)));
|
||||||
},
|
},
|
||||||
|
|
||||||
preventDefault: function(event) {
|
|
||||||
if (event.preventDefault) {
|
|
||||||
event.preventDefault();
|
|
||||||
} else {
|
|
||||||
// IE
|
|
||||||
event.returnValue = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stopPropagation: function(event) {
|
|
||||||
if (event.stopPropagation) {
|
|
||||||
event.stopPropagation();
|
|
||||||
} else {
|
|
||||||
event.cancelBubble = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stop: function(event) {
|
stop: function(event) {
|
||||||
DomEvent.stopPropagation(event);
|
event.stopPropagation();
|
||||||
DomEvent.preventDefault(event);
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -350,7 +350,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
||||||
&& this.fire(type, new ToolEvent(this, type, event));
|
&& this.fire(type, new ToolEvent(this, type, event));
|
||||||
},
|
},
|
||||||
|
|
||||||
_onHandleEvent: function(type, point, event) {
|
_handleEvent: function(type, point, event) {
|
||||||
// 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
|
||||||
|
@ -401,7 +401,9 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Return if a callback was called or not.
|
// Prevent default if mouse event was handled.
|
||||||
|
if (called)
|
||||||
|
event.preventDefault();
|
||||||
return called;
|
return called;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -108,11 +108,12 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
||||||
doubleClick,
|
doubleClick,
|
||||||
clickTime;
|
clickTime;
|
||||||
|
|
||||||
// Returns false if event was stopped, true otherwise, whether handler was
|
// Returns true if event was stopped, false otherwise, whether handler was
|
||||||
// called or not!
|
// called or not!
|
||||||
function callEvent(type, event, point, target, lastPoint, bubble) {
|
function callEvent(type, event, point, target, lastPoint) {
|
||||||
var item = target,
|
var item = target,
|
||||||
mouseEvent;
|
mouseEvent;
|
||||||
|
// Bubble up the DOM and find a parent that responds to this event.
|
||||||
while (item) {
|
while (item) {
|
||||||
if (item.responds(type)) {
|
if (item.responds(type)) {
|
||||||
// Create an reuse the event object if we're bubbling
|
// Create an reuse the event object if we're bubbling
|
||||||
|
@ -120,91 +121,102 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
||||||
mouseEvent = new MouseEvent(type, event, point, target,
|
mouseEvent = new MouseEvent(type, event, point, target,
|
||||||
// Calculate delta if lastPoint was passed
|
// Calculate delta if lastPoint was passed
|
||||||
lastPoint ? point.subtract(lastPoint) : null);
|
lastPoint ? point.subtract(lastPoint) : null);
|
||||||
if (item.fire(type, mouseEvent)
|
if (item.fire(type, mouseEvent) && mouseEvent.isStopped) {
|
||||||
&& (!bubble || mouseEvent._stopped))
|
// Call preventDefault() on native event if mouse event was
|
||||||
return false;
|
// handled here.
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
item = item.getParent();
|
item = item.getParent();
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEvent(view, type, event, point, lastPoint) {
|
return {
|
||||||
if (view._eventCounters[type]) {
|
/**
|
||||||
var project = view._project,
|
* 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, {
|
hit = project.hitTest(point, {
|
||||||
tolerance: project.options.hitTolerance || 0,
|
tolerance: project.options.hitTolerance || 0,
|
||||||
fill: true,
|
fill: true,
|
||||||
stroke: true
|
stroke: true
|
||||||
}),
|
}),
|
||||||
item = hit && hit.item;
|
item = hit && hit.item,
|
||||||
if (item) {
|
stopped = false;
|
||||||
// If this is a mousemove event and we change the overItem,
|
// Now handle the mouse events
|
||||||
// reset lastPoint to point so delta is (0, 0)
|
switch (type) {
|
||||||
if (type === 'mousemove' && item != overItem)
|
case 'mousedown':
|
||||||
lastPoint = point;
|
stopped = callEvent(type, event, point, item);
|
||||||
// If we have a downItem with a mousedrag event, do not send
|
// See if we're clicking again on the same item, within the
|
||||||
// mousemove events to any item while we're dragging.
|
// double-click time. Firefox uses 300ms as the max time
|
||||||
// TODO: Do we also need to lock mousenter / mouseleave in the
|
// difference:
|
||||||
// same way?
|
doubleClick = lastItem == item && (Date.now() - clickTime < 300);
|
||||||
if (type !== 'mousemove' || !hasDrag)
|
downItem = lastItem = item;
|
||||||
callEvent(type, event, point, item, lastPoint);
|
downPoint = lastPoint = overPoint = point;
|
||||||
return item;
|
hasDrag = downItem && downItem.responds('mousedrag');
|
||||||
}
|
break;
|
||||||
}
|
case 'mouseup':
|
||||||
}
|
// stopping mousup events does not prevent mousedrag / mousemove
|
||||||
|
// hanlding here, but it does click / doubleclick
|
||||||
return {
|
stopped = callEvent(type, event, point, item, downPoint);
|
||||||
_onMouseDown: function(event, point) {
|
if (hasDrag) {
|
||||||
var item = handleEvent(this, 'mousedown', event, point);
|
// If the point has changed since the last mousedrag event,
|
||||||
// See if we're clicking again on the same item, within the
|
// send another one
|
||||||
// double-click time. Firefox uses 300ms as the max time difference:
|
if (lastPoint && !lastPoint.equals(point))
|
||||||
doubleClick = lastItem == item && (Date.now() - clickTime < 300);
|
callEvent('mousedrag', event, point, downItem,
|
||||||
downItem = lastItem = item;
|
lastPoint);
|
||||||
downPoint = lastPoint = overPoint = point;
|
// If we end up over another item, send it a mousemove event
|
||||||
hasDrag = downItem && downItem.responds('mousedrag');
|
// now. Use point as overPoint, so delta is (0, 0) since
|
||||||
},
|
// this will be the first mousemove event for this item.
|
||||||
|
if (item !== downItem) {
|
||||||
_onMouseUp: function(event, point) {
|
overPoint = point;
|
||||||
// TODO: Check
|
callEvent('mousemove', event, point, item, overPoint);
|
||||||
var item = handleEvent(this, 'mouseup', event, point);
|
}
|
||||||
if (hasDrag) {
|
|
||||||
// If the point has changed since the last mousedrag event, send
|
|
||||||
// another one
|
|
||||||
if (lastPoint && !lastPoint.equals(point))
|
|
||||||
callEvent('mousedrag', event, point, downItem, lastPoint);
|
|
||||||
// If we had a mousedrag event locking mousemove events and are
|
|
||||||
// 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 != downItem) {
|
|
||||||
overPoint = point;
|
|
||||||
callEvent('mousemove', event, point, item, overPoint);
|
|
||||||
}
|
}
|
||||||
|
if (!stopped && item === downItem) {
|
||||||
|
clickTime = Date.now();
|
||||||
|
callEvent(doubleClick && downItem.responds('doubleclick')
|
||||||
|
? 'doubleclick' : 'click', event, downPoint, item);
|
||||||
|
doubleClick = false;
|
||||||
|
}
|
||||||
|
downItem = null;
|
||||||
|
hasDrag = false;
|
||||||
|
break;
|
||||||
|
case 'mousemove':
|
||||||
|
// Allow both mousedrag and mousemove events to stop mousemove
|
||||||
|
// events from reaching tools.
|
||||||
|
if (hasDrag)
|
||||||
|
stopped = callEvent('mousedrag', event, point, downItem,
|
||||||
|
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(type, event, point, item, overPoint);
|
||||||
|
}
|
||||||
|
lastPoint = overPoint = point;
|
||||||
|
if (item !== overItem) {
|
||||||
|
callEvent('mouseleave', event, point, overItem);
|
||||||
|
overItem = item;
|
||||||
|
callEvent('mouseenter', event, point, item);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (item === downItem) {
|
return stopped;
|
||||||
clickTime = Date.now();
|
|
||||||
if (!doubleClick
|
|
||||||
// callEvent returns false if event is stopped.
|
|
||||||
|| callEvent('doubleclick', event, downPoint, item))
|
|
||||||
callEvent('click', event, downPoint, item);
|
|
||||||
doubleClick = false;
|
|
||||||
}
|
|
||||||
downItem = null;
|
|
||||||
hasDrag = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onMouseMove: function(event, point) {
|
|
||||||
// Call the mousedrag event first if an item was clicked earlier
|
|
||||||
if (downItem)
|
|
||||||
callEvent('mousedrag', event, point, downItem, lastPoint);
|
|
||||||
var item = handleEvent(this, 'mousemove', event, point, overPoint);
|
|
||||||
lastPoint = overPoint = point;
|
|
||||||
if (item !== overItem) {
|
|
||||||
callEvent('mouseleave', event, point, overItem);
|
|
||||||
overItem = item;
|
|
||||||
callEvent('mouseenter', event, point, item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,14 +21,17 @@ var Event = Base.extend(/** @lends Event# */{
|
||||||
this.event = event;
|
this.event = event;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isPrevented: false,
|
||||||
|
isStopped: false,
|
||||||
|
|
||||||
preventDefault: function() {
|
preventDefault: function() {
|
||||||
this._prevented = true;
|
this.isPrevented = true;
|
||||||
DomEvent.preventDefault(this.event);
|
this.event.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
stopPropagation: function() {
|
stopPropagation: function() {
|
||||||
this._stopped = true;
|
this.isStopped = true;
|
||||||
DomEvent.stopPropagation(this.event);
|
this.event.stopPropagation();
|
||||||
},
|
},
|
||||||
|
|
||||||
stop: function() {
|
stop: function() {
|
||||||
|
|
|
@ -637,10 +637,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
dragging = true;
|
dragging = true;
|
||||||
// Always first call the view's mouse handlers, as required by
|
// Always first call the view's mouse handlers, as required by
|
||||||
// CanvasView, and then handle the active tool, if any.
|
// CanvasView, and then handle the active tool, if any.
|
||||||
if (view._onMouseDown)
|
view._handleEvent('mousedown', point, event);
|
||||||
view._onMouseDown(event, point);
|
|
||||||
if (tool = view._scope._tool)
|
if (tool = view._scope._tool)
|
||||||
tool._onHandleEvent('mousedown', point, event);
|
tool._handleEvent('mousedown', point, event);
|
||||||
// In the end we always call draw(), but pass checkRedraw = true, so we
|
// In the end we always call draw(), but pass checkRedraw = true, so we
|
||||||
// only redraw the view if anything has changed in the above calls.
|
// only redraw the view if anything has changed in the above calls.
|
||||||
view.draw(true);
|
view.draw(true);
|
||||||
|
@ -668,13 +667,11 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
var point = event && viewToProject(view, event);
|
var point = event && viewToProject(view, event);
|
||||||
if (dragging || new Rectangle(new Point(),
|
if (dragging || new Rectangle(new Point(),
|
||||||
view.getViewSize()).contains(point)) {
|
view.getViewSize()).contains(point)) {
|
||||||
if (view._onMouseMove)
|
view._handleEvent('mousemove', point, event);
|
||||||
view._onMouseMove(event, point);
|
|
||||||
if (tool = view._scope._tool) {
|
if (tool = view._scope._tool) {
|
||||||
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
||||||
if (tool._onHandleEvent(dragging && tool.responds('mousedrag')
|
tool._handleEvent(dragging && tool.responds('mousedrag')
|
||||||
? 'mousedrag' : 'mousemove', point, event))
|
? 'mousedrag' : 'mousemove', point, event);
|
||||||
DomEvent.stop(event);
|
|
||||||
}
|
}
|
||||||
view.draw(true);
|
view.draw(true);
|
||||||
}
|
}
|
||||||
|
@ -687,11 +684,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
var point = viewToProject(view, event);
|
var point = viewToProject(view, event);
|
||||||
curPoint = null;
|
curPoint = null;
|
||||||
dragging = false;
|
dragging = false;
|
||||||
if (view._onMouseUp)
|
view._handleEvent('mouseup', point, event);
|
||||||
view._onMouseUp(event, point);
|
if (tool)
|
||||||
// Cancel DOM-event if it was handled by our tool
|
tool._handleEvent('mouseup', point, event);
|
||||||
if (tool && tool._onHandleEvent('mouseup', point, event))
|
|
||||||
DomEvent.stop(event);
|
|
||||||
view.draw(true);
|
view.draw(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,7 +694,7 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
// Only stop this even if we're dragging already, since otherwise no
|
// Only stop this even if we're dragging already, since otherwise no
|
||||||
// text whatsoever can be selected on the page.
|
// text whatsoever can be selected on the page.
|
||||||
if (dragging)
|
if (dragging)
|
||||||
DomEvent.stop(event);
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
// mousemove and mouseup events need to be installed on document, not the
|
// mousemove and mouseup events need to be installed on document, not the
|
||||||
|
@ -727,6 +722,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
selectstart: selectstart
|
selectstart: selectstart
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// To be defined in subclasses
|
||||||
|
_handleEvent: function(/* type, point, event */) {},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
/**
|
/**
|
||||||
* Loops through all views and sets the focus on the first
|
* Loops through all views and sets the focus on the first
|
||||||
|
|
Loading…
Reference in a new issue