mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05: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
|
||||
// behaviour and stop propagation of the event by calling stop()
|
||||
if (handlers[i].apply(that, args) === false
|
||||
&& event && event.stop)
|
||||
&& event && event.stop) {
|
||||
event.stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// See PaperScript.handleException for an explanation of the following.
|
||||
|
@ -135,9 +137,7 @@ var Callback = {
|
|||
types[type] = isString ? {} : entry;
|
||||
// Create getters and setters for the property
|
||||
// with the on*-name name:
|
||||
// Use '__' as there are some _onMouse* functions
|
||||
// already, e.g.g on View.
|
||||
name = '__' + name;
|
||||
name = '_' + name;
|
||||
src['get' + part] = function() {
|
||||
return this[name];
|
||||
};
|
||||
|
|
|
@ -48,26 +48,9 @@ var DomEvent = {
|
|||
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) {
|
||||
DomEvent.stopPropagation(event);
|
||||
DomEvent.preventDefault(event);
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -350,7 +350,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
|||
&& this.fire(type, new ToolEvent(this, type, event));
|
||||
},
|
||||
|
||||
_onHandleEvent: function(type, point, event) {
|
||||
_handleEvent: function(type, point, event) {
|
||||
// Update global reference to this scope.
|
||||
paper = this._scope;
|
||||
// Now handle event callbacks
|
||||
|
@ -401,7 +401,9 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
|||
}
|
||||
break;
|
||||
}
|
||||
// Return if a callback was called or not.
|
||||
// Prevent default if mouse event was handled.
|
||||
if (called)
|
||||
event.preventDefault();
|
||||
return called;
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -108,11 +108,12 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
|||
doubleClick,
|
||||
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!
|
||||
function callEvent(type, event, point, target, lastPoint, bubble) {
|
||||
function callEvent(type, event, point, target, lastPoint) {
|
||||
var item = target,
|
||||
mouseEvent;
|
||||
// Bubble up the DOM and find a parent that responds to this event.
|
||||
while (item) {
|
||||
if (item.responds(type)) {
|
||||
// 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,
|
||||
// Calculate delta if lastPoint was passed
|
||||
lastPoint ? point.subtract(lastPoint) : null);
|
||||
if (item.fire(type, mouseEvent)
|
||||
&& (!bubble || mouseEvent._stopped))
|
||||
return false;
|
||||
if (item.fire(type, mouseEvent) && mouseEvent.isStopped) {
|
||||
// Call preventDefault() on native event if mouse event was
|
||||
// handled here.
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
item = item.getParent();
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleEvent(view, type, event, point, lastPoint) {
|
||||
if (view._eventCounters[type]) {
|
||||
var project = view._project,
|
||||
return {
|
||||
/**
|
||||
* 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: project.options.hitTolerance || 0,
|
||||
fill: true,
|
||||
stroke: true
|
||||
}),
|
||||
item = hit && hit.item;
|
||||
if (item) {
|
||||
// If this is a mousemove event and we change the overItem,
|
||||
// reset lastPoint to point so delta is (0, 0)
|
||||
if (type === 'mousemove' && item != overItem)
|
||||
lastPoint = point;
|
||||
// If we have a downItem with a mousedrag event, do not send
|
||||
// mousemove events to any item while we're dragging.
|
||||
// TODO: Do we also need to lock mousenter / mouseleave in the
|
||||
// same way?
|
||||
if (type !== 'mousemove' || !hasDrag)
|
||||
callEvent(type, event, point, item, lastPoint);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
_onMouseDown: function(event, point) {
|
||||
var item = handleEvent(this, 'mousedown', event, point);
|
||||
// See if we're clicking again on the same item, within the
|
||||
// double-click time. Firefox uses 300ms as the max time difference:
|
||||
doubleClick = lastItem == item && (Date.now() - clickTime < 300);
|
||||
downItem = lastItem = item;
|
||||
downPoint = lastPoint = overPoint = point;
|
||||
hasDrag = downItem && downItem.responds('mousedrag');
|
||||
},
|
||||
|
||||
_onMouseUp: function(event, point) {
|
||||
// TODO: Check
|
||||
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);
|
||||
item = hit && hit.item,
|
||||
stopped = false;
|
||||
// Now handle the mouse events
|
||||
switch (type) {
|
||||
case 'mousedown':
|
||||
stopped = callEvent(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:
|
||||
doubleClick = lastItem == item && (Date.now() - clickTime < 300);
|
||||
downItem = lastItem = item;
|
||||
downPoint = lastPoint = overPoint = point;
|
||||
hasDrag = downItem && downItem.responds('mousedrag');
|
||||
break;
|
||||
case 'mouseup':
|
||||
// stopping mousup events does not prevent mousedrag / mousemove
|
||||
// hanlding here, but it does click / doubleclick
|
||||
stopped = callEvent(type, event, point, item, downPoint);
|
||||
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 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 !== 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) {
|
||||
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);
|
||||
}
|
||||
return stopped;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -21,14 +21,17 @@ var Event = Base.extend(/** @lends Event# */{
|
|||
this.event = event;
|
||||
},
|
||||
|
||||
isPrevented: false,
|
||||
isStopped: false,
|
||||
|
||||
preventDefault: function() {
|
||||
this._prevented = true;
|
||||
DomEvent.preventDefault(this.event);
|
||||
this.isPrevented = true;
|
||||
this.event.preventDefault();
|
||||
},
|
||||
|
||||
stopPropagation: function() {
|
||||
this._stopped = true;
|
||||
DomEvent.stopPropagation(this.event);
|
||||
this.isStopped = true;
|
||||
this.event.stopPropagation();
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
|
|
|
@ -637,10 +637,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
dragging = true;
|
||||
// Always first call the view's mouse handlers, as required by
|
||||
// CanvasView, and then handle the active tool, if any.
|
||||
if (view._onMouseDown)
|
||||
view._onMouseDown(event, point);
|
||||
view._handleEvent('mousedown', point, event);
|
||||
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
|
||||
// only redraw the view if anything has changed in the above calls.
|
||||
view.draw(true);
|
||||
|
@ -668,13 +667,11 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
var point = event && viewToProject(view, event);
|
||||
if (dragging || new Rectangle(new Point(),
|
||||
view.getViewSize()).contains(point)) {
|
||||
if (view._onMouseMove)
|
||||
view._onMouseMove(event, point);
|
||||
view._handleEvent('mousemove', point, event);
|
||||
if (tool = view._scope._tool) {
|
||||
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
||||
if (tool._onHandleEvent(dragging && tool.responds('mousedrag')
|
||||
? 'mousedrag' : 'mousemove', point, event))
|
||||
DomEvent.stop(event);
|
||||
tool._handleEvent(dragging && tool.responds('mousedrag')
|
||||
? 'mousedrag' : 'mousemove', point, event);
|
||||
}
|
||||
view.draw(true);
|
||||
}
|
||||
|
@ -687,11 +684,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
|||
var point = viewToProject(view, event);
|
||||
curPoint = null;
|
||||
dragging = false;
|
||||
if (view._onMouseUp)
|
||||
view._onMouseUp(event, point);
|
||||
// Cancel DOM-event if it was handled by our tool
|
||||
if (tool && tool._onHandleEvent('mouseup', point, event))
|
||||
DomEvent.stop(event);
|
||||
view._handleEvent('mouseup', point, event);
|
||||
if (tool)
|
||||
tool._handleEvent('mouseup', point, event);
|
||||
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
|
||||
// text whatsoever can be selected on the page.
|
||||
if (dragging)
|
||||
DomEvent.stop(event);
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
// 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
|
||||
},
|
||||
|
||||
// To be defined in subclasses
|
||||
_handleEvent: function(/* type, point, event */) {},
|
||||
|
||||
statics: {
|
||||
/**
|
||||
* Loops through all views and sets the focus on the first
|
||||
|
|
Loading…
Reference in a new issue