mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-06 04:42:15 -05:00
Make sure mouse events propagate to the view while their targets remain consistent.
Closes #995
This commit is contained in:
parent
93e4d81645
commit
724bcb2e35
2 changed files with 76 additions and 45 deletions
|
@ -30,7 +30,7 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.event = event;
|
this.event = event;
|
||||||
this.point = point;
|
this.point = point;
|
||||||
this.target = target;
|
this._target = target;
|
||||||
this.delta = delta;
|
this.delta = delta;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -56,6 +56,12 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
|
||||||
* @name MouseEvent#target
|
* @name MouseEvent#target
|
||||||
* @type Item
|
* @type Item
|
||||||
*/
|
*/
|
||||||
|
getTarget: function() {
|
||||||
|
var target = this._target;
|
||||||
|
if (typeof target === 'function')
|
||||||
|
target = this._target = target();
|
||||||
|
return target;
|
||||||
|
},
|
||||||
|
|
||||||
// DOCS: document MouseEvent#delta
|
// DOCS: document MouseEvent#delta
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +75,7 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
|
||||||
toString: function() {
|
toString: function() {
|
||||||
return "{ type: '" + this.type
|
return "{ type: '" + this.type
|
||||||
+ "', point: " + this.point
|
+ "', point: " + this.point
|
||||||
+ ', target: ' + this.target
|
+ ', target: ' + this.getTarget()
|
||||||
+ (this.delta ? ', delta: ' + this.delta : '')
|
+ (this.delta ? ', delta: ' + this.delta : '')
|
||||||
+ ', modifiers: ' + this.getModifiers()
|
+ ', modifiers: ' + this.getModifiers()
|
||||||
+ ' }';
|
+ ' }';
|
||||||
|
|
111
src/view/View.js
111
src/view/View.js
|
@ -1155,9 +1155,9 @@ new function() { // Injection scope for event handling on the browser
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true if event was prevented, false otherwise.
|
// Returns true if event was prevented, false otherwise.
|
||||||
function emitMouseEvent(obj, type, event, point, prevPoint, stopItem) {
|
function emitMouseEvent(obj, target, type, event, point, prevPoint,
|
||||||
var target = obj,
|
stopItem) {
|
||||||
stopped = false,
|
var stopped = false,
|
||||||
mouseEvent;
|
mouseEvent;
|
||||||
|
|
||||||
// Returns true if the event was stopped, false otherwise.
|
// Returns true if the event was stopped, false otherwise.
|
||||||
|
@ -1166,7 +1166,8 @@ new function() { // Injection scope for event handling on the browser
|
||||||
// Only produce the event object if we really need it, and then
|
// Only produce the event object if we really need it, and then
|
||||||
// reuse it if we're bubbling.
|
// reuse it if we're bubbling.
|
||||||
if (!mouseEvent) {
|
if (!mouseEvent) {
|
||||||
mouseEvent = new MouseEvent(type, event, point, target,
|
mouseEvent = new MouseEvent(type, event, point,
|
||||||
|
target || obj,
|
||||||
// Calculate delta if prevPoint was passed
|
// Calculate delta if prevPoint was passed
|
||||||
prevPoint ? point.subtract(prevPoint) : null);
|
prevPoint ? point.subtract(prevPoint) : null);
|
||||||
}
|
}
|
||||||
|
@ -1195,7 +1196,8 @@ new function() { // Injection scope for event handling on the browser
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if event was stopped, false otherwise.
|
// Returns true if event was stopped, false otherwise.
|
||||||
function emitMouseEvents(view, item, type, event, point, prevPoint) {
|
function emitMouseEvents(view, hitItem, hitTest, type, event, point,
|
||||||
|
prevPoint) {
|
||||||
// Before handling events, process removeOn() calls for cleanup.
|
// Before handling events, process removeOn() calls for cleanup.
|
||||||
// NOTE: As soon as there is one event handler receiving mousedrag
|
// NOTE: As soon as there is one event handler receiving mousedrag
|
||||||
// events, non of the removeOnMove() items will be removed while the
|
// events, non of the removeOnMove() items will be removed while the
|
||||||
|
@ -1206,17 +1208,21 @@ new function() { // Injection scope for event handling on the browser
|
||||||
// and of the handlers called event.preventDefault()
|
// and of the handlers called event.preventDefault()
|
||||||
prevented = called = false;
|
prevented = called = false;
|
||||||
// First handle the drag-item and its parents, through bubbling.
|
// First handle the drag-item and its parents, through bubbling.
|
||||||
return (dragItem && emitMouseEvent(dragItem, type, event, point,
|
return (dragItem && emitMouseEvent(dragItem, null, type, event,
|
||||||
prevPoint)
|
point, prevPoint)
|
||||||
// Next handle the hit-test item, if it's different from the drag
|
// Next handle the hit-item, if it's different from the drag-item
|
||||||
// item and not a descendant of it (in which case it would already
|
// and not a descendant of it (in which case it would already have
|
||||||
// have received an event in the call above). Use fallbacks to
|
// received an event in the call above). Use fallbacks to translate
|
||||||
// translate mousedrag to mousemove, since drag is handled above.
|
// mousedrag to mousemove, since drag is handled above.
|
||||||
|| item && item !== dragItem && !item.isDescendant(dragItem)
|
|| hitItem && hitItem !== dragItem
|
||||||
&& emitMouseEvent(item, fallbacks[type] || type, event, point,
|
&& !hitItem.isDescendant(dragItem)
|
||||||
prevPoint, dragItem)
|
&& emitMouseEvent(hitItem, null, fallbacks[type] || type, event,
|
||||||
|
point, prevPoint, dragItem)
|
||||||
// Lastly handle the mouse events on the view, if we're still here.
|
// Lastly handle the mouse events on the view, if we're still here.
|
||||||
|| emitMouseEvent(view, type, event, point, prevPoint));
|
// Choose from the potential targets in the right sequence, with the
|
||||||
|
// hitTest() function as the fall-back getter for MouseEvent#target.
|
||||||
|
|| emitMouseEvent(view, dragItem || hitItem || hitTest, type, event,
|
||||||
|
point, prevPoint));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1293,12 +1299,7 @@ new function() { // Injection scope for event handling on the browser
|
||||||
// Run the hit-test on items first, but only if we're required to do
|
// Run the hit-test on items first, but only if we're required to do
|
||||||
// so for this given mouse event, see hitItems, #_countItemEvent():
|
// so for this given mouse event, see hitItems, #_countItemEvent():
|
||||||
var inView = this.getBounds().contains(point),
|
var inView = this.getBounds().contains(point),
|
||||||
hit = hitItems && inView && this._project.hitTest(point, {
|
hitItem = undefined,
|
||||||
tolerance: 0,
|
|
||||||
fill: true,
|
|
||||||
stroke: true
|
|
||||||
}),
|
|
||||||
item = hit && hit.item || null,
|
|
||||||
// Keep track if view event should be handled, so we can use it
|
// Keep track if view event should be handled, so we can use it
|
||||||
// to decide if tool._handleMouseEvent() shall be called after.
|
// to decide if tool._handleMouseEvent() shall be called after.
|
||||||
handle = false,
|
handle = false,
|
||||||
|
@ -1307,34 +1308,56 @@ new function() { // Injection scope for event handling on the browser
|
||||||
// mouse event types.
|
// mouse event types.
|
||||||
mouse[type.substr(5)] = true;
|
mouse[type.substr(5)] = true;
|
||||||
|
|
||||||
// Always first call the view's mouse handlers, as required by
|
// Provide a hit-test function that makes sure to only perform the
|
||||||
// CanvasView, and then handle the active tool after, if any.
|
// hit-test once, and only when it's actually required. This method
|
||||||
if (hitItems && item !== overItem) {
|
// is passed to emitMouseEvents() and as target to emitMouseEvent(),
|
||||||
// But first handle mouseenter / leave between items and also on
|
// as the fall-back getter for MouseEvent#target.
|
||||||
// the view, but only if hitItems is true, see above.
|
function hitTest() {
|
||||||
|
//
|
||||||
|
if (hitItem === undefined) {
|
||||||
|
var hit = inView && view._project.hitTest(point, {
|
||||||
|
tolerance: 0,
|
||||||
|
fill: true,
|
||||||
|
stroke: true
|
||||||
|
});
|
||||||
|
hitItem = hit && hit.item || null;
|
||||||
|
}
|
||||||
|
// Return the target with view as the fall-back, as expected by
|
||||||
|
// MouseEvent#target.
|
||||||
|
return hitItem || view;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute hitTest right away if we have events relying on hitItem.
|
||||||
|
if (hitItems)
|
||||||
|
hitTest();
|
||||||
|
// Handle mouseenter / leave between items and views first.
|
||||||
|
if (hitItems && hitItem !== overItem) {
|
||||||
if (overItem) {
|
if (overItem) {
|
||||||
emitMouseEvent(overItem, 'mouseleave', event, point);
|
emitMouseEvent(overItem, null, 'mouseleave', event, point);
|
||||||
}
|
}
|
||||||
if (item) {
|
if (hitItem) {
|
||||||
emitMouseEvent(item, 'mouseenter', event, point);
|
emitMouseEvent(hitItem, null, 'mouseenter', event, point);
|
||||||
}
|
}
|
||||||
overItem = item;
|
overItem = hitItem;
|
||||||
}
|
}
|
||||||
// Handle mouseenter / leave on the view.
|
// Handle mouseenter / leave on the view.
|
||||||
if (wasInView ^ inView) {
|
if (wasInView ^ inView) {
|
||||||
emitMouseEvent(this, inView ? 'mouseenter' : 'mouseleave',
|
emitMouseEvent(this, null, inView ? 'mouseenter' : 'mouseleave',
|
||||||
event, point);
|
event, point);
|
||||||
overView = inView ? this : null;
|
overView = inView ? this : null;
|
||||||
handle = true; // To include the leaving move.
|
handle = true; // To include the leaving move.
|
||||||
}
|
}
|
||||||
// Now finally handle the mousemove / mousedrag event.
|
// Now handle the mousemove / mousedrag event.
|
||||||
|
// Always call the view's mouse handlers first, as required by
|
||||||
|
// CanvasView, and then handle the active tool after, if any.
|
||||||
// mousedrag is allowed to leave the view and still triggers events,
|
// mousedrag is allowed to leave the view and still triggers events,
|
||||||
// but do not trigger two subsequent even with the same location.
|
// but do not trigger two subsequent even with the same location.
|
||||||
if ((inView || mouse.drag) && !point.equals(lastPoint)) {
|
if ((inView || mouse.drag) && !point.equals(lastPoint)) {
|
||||||
// Handle mousemove even if this is not actually a mousemove
|
// Handle mousemove even if this is not actually a mousemove
|
||||||
// event but the mouse has moved since the last event.
|
// event but the mouse has moved since the last event.
|
||||||
emitMouseEvents(this, item, nativeMove ? type : 'mousemove',
|
emitMouseEvents(this, hitItem, hitTest,
|
||||||
event, point, lastPoint);
|
nativeMove ? type : 'mousemove', event,
|
||||||
|
point, lastPoint);
|
||||||
handle = true;
|
handle = true;
|
||||||
}
|
}
|
||||||
wasInView = inView;
|
wasInView = inView;
|
||||||
|
@ -1342,25 +1365,27 @@ new function() { // Injection scope for event handling on the browser
|
||||||
// We emit mousedown only when in the view, and mouseup regardless,
|
// We emit mousedown only when in the view, and mouseup regardless,
|
||||||
// as long as the mousedown event was inside.
|
// as long as the mousedown event was inside.
|
||||||
if (mouse.down && inView || mouse.up && downPoint) {
|
if (mouse.down && inView || mouse.up && downPoint) {
|
||||||
emitMouseEvents(this, item, type, event, point, downPoint);
|
emitMouseEvents(this, hitItem, hitTest, type, event,
|
||||||
|
point, downPoint);
|
||||||
if (mouse.down) {
|
if (mouse.down) {
|
||||||
// See if we're clicking again on the same item, within the
|
// See if we're clicking again on the same item, within the
|
||||||
// double-click time. Firefox uses 300ms as the max time
|
// double-click time. Firefox uses 300ms as the max time
|
||||||
// difference:
|
// difference:
|
||||||
dblClick = item === clickItem
|
dblClick = hitItem === clickItem
|
||||||
&& (Date.now() - clickTime < 300);
|
&& (Date.now() - clickTime < 300);
|
||||||
downItem = clickItem = item;
|
downItem = clickItem = hitItem;
|
||||||
// Only start dragging if the mousedown event has not
|
// Only start dragging if the mousedown event has not
|
||||||
// prevented the default.
|
// prevented the default.
|
||||||
dragItem = !prevented && item;
|
dragItem = !prevented && hitItem;
|
||||||
downPoint = point;
|
downPoint = point;
|
||||||
} else if (mouse.up) {
|
} else if (mouse.up) {
|
||||||
// Emulate click / doubleclick, but only on item, not view
|
// Emulate click / doubleclick, but only on the hit-item,
|
||||||
if (!prevented && item === downItem) {
|
// not the view.
|
||||||
|
if (!prevented && hitItem === downItem) {
|
||||||
clickTime = Date.now();
|
clickTime = Date.now();
|
||||||
emitMouseEvents(this, item,
|
emitMouseEvents(this, hitItem, hitTest,
|
||||||
dblClick ? 'doubleclick' : 'click',
|
dblClick ? 'doubleclick' : 'click', event,
|
||||||
event, point, downPoint);
|
point, downPoint);
|
||||||
dblClick = false;
|
dblClick = false;
|
||||||
}
|
}
|
||||||
downItem = dragItem = null;
|
downItem = dragItem = null;
|
||||||
|
|
Loading…
Reference in a new issue