mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-05 20:32:00 -05:00
Further overhaul and streamline handling of view and tool mouse-events.
This commit is contained in:
parent
0743f1b7b8
commit
d5f2ff479d
5 changed files with 90 additions and 108 deletions
|
@ -572,7 +572,7 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{
|
||||||
// or by looking at the amount of elements in the arguments list,
|
// or by looking at the amount of elements in the arguments list,
|
||||||
// or the passed array:
|
// or the passed array:
|
||||||
return arg && arg.width !== undefined
|
return arg && arg.width !== undefined
|
||||||
|| (Array.isArray(arg) ? arg : arguments).length == 4
|
|| (Array.isArray(arg) ? arg : arguments).length === 4
|
||||||
? this._containsRectangle(Rectangle.read(arguments))
|
? this._containsRectangle(Rectangle.read(arguments))
|
||||||
: this._containsPoint(Point.read(arguments));
|
: this._containsPoint(Point.read(arguments));
|
||||||
},
|
},
|
||||||
|
|
|
@ -257,12 +257,15 @@ var PaperScope = Base.extend(/** @lends PaperScope# */{
|
||||||
clear: function() {
|
clear: function() {
|
||||||
// Remove all projects, views and tools.
|
// Remove all projects, views and tools.
|
||||||
// This also removes the installed event handlers.
|
// This also removes the installed event handlers.
|
||||||
for (var i = this.projects.length - 1; i >= 0; i--)
|
var projects = this.projects,
|
||||||
this.projects[i].remove();
|
tools = this.tools,
|
||||||
for (var i = this.tools.length - 1; i >= 0; i--)
|
palettes = this.palettes;
|
||||||
this.tools[i].remove();
|
for (var i = projects.length - 1; i >= 0; i--)
|
||||||
for (var i = this.palettes.length - 1; i >= 0; i--)
|
projects[i].remove();
|
||||||
this.palettes[i].remove();
|
for (var i = tools.length - 1; i >= 0; i--)
|
||||||
|
tools[i].remove();
|
||||||
|
for (var i = palettes.length - 1; i >= 0; i--)
|
||||||
|
palettes[i].remove();
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: function() {
|
remove: function() {
|
||||||
|
|
|
@ -136,11 +136,11 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
||||||
function(name) {
|
function(name) {
|
||||||
this[name] = {
|
this[name] = {
|
||||||
install: function(type) {
|
install: function(type) {
|
||||||
this.getView()._installEvent(type);
|
this.getView()._countItemEvent(type, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
uninstall: function(type) {
|
uninstall: function(type) {
|
||||||
this.getView()._uninstallEvent(type);
|
this.getView()._countItemEvent(type, -1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -46,9 +46,9 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
|
||||||
_class: 'Tool',
|
_class: 'Tool',
|
||||||
_list: 'tools',
|
_list: 'tools',
|
||||||
_reference: 'tool',
|
_reference: 'tool',
|
||||||
_events: [ 'onActivate', 'onDeactivate', 'onEditOptions',
|
_events: ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
|
||||||
'onMouseDown', 'onMouseUp', 'onMouseDrag', 'onMouseMove',
|
'onActivate', 'onDeactivate', 'onEditOptions', 'onKeyDown',
|
||||||
'onKeyDown', 'onKeyUp' ],
|
'onKeyUp'],
|
||||||
|
|
||||||
// DOCS: rewrite Tool constructor explanation
|
// DOCS: rewrite Tool constructor explanation
|
||||||
initialize: function Tool(props) {
|
initialize: function Tool(props) {
|
||||||
|
|
171
src/view/View.js
171
src/view/View.js
|
@ -115,8 +115,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().
|
// Count the installed item events, see _countItemEvent().
|
||||||
this._eventCounters = {};
|
this._itemEvents = {};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -152,15 +152,7 @@ var View = Base.extend(Emitter, /** @lends View# */{
|
||||||
_events: Base.each(['onResize', 'onMouseDown', 'onMouseUp', 'onMouseMove',
|
_events: Base.each(['onResize', 'onMouseDown', 'onMouseUp', 'onMouseMove',
|
||||||
'onMouseDrag', 'onMouseEnter', 'onMouseLeave'],
|
'onMouseDrag', 'onMouseEnter', 'onMouseLeave'],
|
||||||
function(name) {
|
function(name) {
|
||||||
this[name] = {
|
this[name] = {};
|
||||||
install: function(type) {
|
|
||||||
this._installEvent(type);
|
|
||||||
},
|
|
||||||
|
|
||||||
uninstall: function(type) {
|
|
||||||
this._uninstallEvent(type);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, {
|
}, {
|
||||||
onFrame: {
|
onFrame: {
|
||||||
install: function() {
|
install: function() {
|
||||||
|
@ -704,8 +696,7 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
* delegation to view and tool objects.
|
* delegation to view and tool objects.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var tool,
|
var prevFocus,
|
||||||
prevFocus,
|
|
||||||
tempFocus,
|
tempFocus,
|
||||||
mouseDown = false;
|
mouseDown = false;
|
||||||
|
|
||||||
|
@ -730,38 +721,8 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEvent(type, view, event, point) {
|
|
||||||
var eventType = type === 'mousemove' && mouseDown ? 'mousedrag' : type,
|
|
||||||
project = paper.project,
|
|
||||||
tool = view._scope.tool,
|
|
||||||
prevent = false;
|
|
||||||
|
|
||||||
function handle(obj) {
|
|
||||||
prevent = obj._handleEvent(eventType, event, point) || prevent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!point)
|
|
||||||
point = view.getEventPoint(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);
|
|
||||||
// Prevent default if at least one mouse event handler was called, to
|
|
||||||
// prevent scrolling on touch devices.
|
|
||||||
if (prevent)
|
|
||||||
event.preventDefault();
|
|
||||||
// In the end we always call update(), which only updates the view if
|
|
||||||
// anything has changed in the above calls.
|
|
||||||
view.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMouseMove(view, event, point) {
|
function handleMouseMove(view, event, point) {
|
||||||
handleEvent('mousemove', view, event, point);
|
view._handleEvent('mousemove', event, point);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Touch handling inspired by Hammer.js
|
// Touch handling inspired by Hammer.js
|
||||||
|
@ -822,7 +783,7 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
// should receive keyboard input.
|
// should receive keyboard input.
|
||||||
var view = View._focused = getView(event);
|
var view = View._focused = getView(event);
|
||||||
mouseDown = true;
|
mouseDown = true;
|
||||||
handleEvent('mousedown', view, event);
|
view._handleEvent('mousedown', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
docEvents[mousemove] = function(event) {
|
docEvents[mousemove] = function(event) {
|
||||||
|
@ -853,7 +814,7 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
docEvents[mouseup] = function(event) {
|
docEvents[mouseup] = function(event) {
|
||||||
var view = View._focused;
|
var view = View._focused;
|
||||||
if (view && mouseDown)
|
if (view && mouseDown)
|
||||||
handleEvent('mouseup', view, event);
|
view._handleEvent('mouseup', event);
|
||||||
mouseDown = false;
|
mouseDown = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -877,17 +838,7 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
fallbacks = {
|
fallbacks = {
|
||||||
doubleclick: 'click',
|
doubleclick: 'click',
|
||||||
mousedrag: 'mousemove'
|
mousedrag: 'mousemove'
|
||||||
},
|
};
|
||||||
downPoint,
|
|
||||||
lastPoint,
|
|
||||||
downItem,
|
|
||||||
overItem,
|
|
||||||
dragItem,
|
|
||||||
clickItem,
|
|
||||||
clickTime,
|
|
||||||
dblClick,
|
|
||||||
overView;
|
|
||||||
|
|
||||||
|
|
||||||
// Returns true if event was stopped, false otherwise, whether handler was
|
// Returns true if event was stopped, false otherwise, whether handler was
|
||||||
// called or not!
|
// called or not!
|
||||||
|
@ -951,11 +902,11 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags defining which native events are required by which Paper events
|
* Lookup defining which native events are required by which item events.
|
||||||
* as required for counting amount of necessary natives events.
|
* Required by code that is counting the amount of required natives events.
|
||||||
* The mapping is native -> virtual
|
* The mapping is native -> virtual.
|
||||||
*/
|
*/
|
||||||
var mouseFlags = {
|
var itemEvents = {
|
||||||
mousedown: {
|
mousedown: {
|
||||||
mousedown: 1,
|
mousedown: 1,
|
||||||
mousedrag: 1,
|
mousedrag: 1,
|
||||||
|
@ -976,30 +927,57 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various variables required by #_handleEvent()
|
||||||
|
*/
|
||||||
|
var downPoint,
|
||||||
|
lastPoint,
|
||||||
|
downItem,
|
||||||
|
overItem,
|
||||||
|
dragItem,
|
||||||
|
clickItem,
|
||||||
|
clickTime,
|
||||||
|
dblClick,
|
||||||
|
overView;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_viewEvents: viewEvents,
|
_viewEvents: viewEvents,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private method to handle view and item events.
|
* Private method to handle view and item events.
|
||||||
*
|
|
||||||
* @return {@true if the default event should be prevented}. This is if
|
|
||||||
* at least one event handler was called and none of the called
|
|
||||||
* handlers wants to enforce the default.
|
|
||||||
*/
|
*/
|
||||||
_handleEvent: function(type, event, point) {
|
_handleEvent: function(type, event, point) {
|
||||||
// Run the hit-test first
|
var handleItems = this._itemEvents[type],
|
||||||
var hit = this._project.hitTest(point, {
|
project = paper.project,
|
||||||
|
tool = this._scope.tool;
|
||||||
|
// If it's a native mousemove event but the mouse is clicke, convert
|
||||||
|
// it to a mousedrag.
|
||||||
|
// NOTE: emitEvent(), as well as Tool#_handleEvent() fall back to
|
||||||
|
// mousemove if the objects don't respond to mousedrag.
|
||||||
|
if (type === 'mousemove' && mouseDown)
|
||||||
|
type = 'mousedrag';
|
||||||
|
// Before handling events, process removeOn() calls for cleanup.
|
||||||
|
if (project)
|
||||||
|
project.removeOn(type);
|
||||||
|
if (!point)
|
||||||
|
point = this.getEventPoint(event);
|
||||||
|
// Run the hit-test on items first, but only if we're required to do
|
||||||
|
// so for this given mouse event, see #_countItemEvent().
|
||||||
|
var hit = handleItems && this._project.hitTest(point, {
|
||||||
tolerance: 0,
|
tolerance: 0,
|
||||||
fill: true,
|
fill: true,
|
||||||
stroke: true
|
stroke: true
|
||||||
}),
|
}),
|
||||||
item = hit && hit.item,
|
item = hit && hit.item || undefined,
|
||||||
inView = this.getBounds().contains(point),
|
inView = this.getBounds().contains(point),
|
||||||
|
wasInView = this === overView,
|
||||||
stopped = false,
|
stopped = false,
|
||||||
mouse = {};
|
mouse = {};
|
||||||
// Create a simple lookup object to quickly check for different
|
// Create a simple lookup object to quickly check for different
|
||||||
// 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
|
||||||
|
// CanvasView, and then handle the active tool after, if any.
|
||||||
// Handle mousemove first, even if this is not actually a mousemove
|
// Handle mousemove first, even if this is not actually a mousemove
|
||||||
// event but the mouse has moved since the last event, but do not
|
// event but the mouse has moved since the last event, but do not
|
||||||
// allow it to stop the other events in that case.
|
// allow it to stop the other events in that case.
|
||||||
|
@ -1015,13 +993,10 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
emitEvent(item, 'mouseenter', event, point);
|
emitEvent(item, 'mouseenter', event, point);
|
||||||
}
|
}
|
||||||
overItem = item;
|
overItem = item;
|
||||||
if (overView && !overView.getBounds().contains(point)) {
|
if (wasInView ^ inView) {
|
||||||
emitEvent(overView, 'mouseleave', event, point);
|
emitEvent(this, inView ? 'mouseenter' : 'mouseleave', event,
|
||||||
overView = null;
|
point);
|
||||||
}
|
overView = inView ? this : null;
|
||||||
if (this !== overView && inView) {
|
|
||||||
emitEvent(this, 'mouseenter', event, point);
|
|
||||||
overView = this;
|
|
||||||
}
|
}
|
||||||
if (inView || mouse.drag && !lastPoint.equals(point))
|
if (inView || mouse.drag && !lastPoint.equals(point))
|
||||||
stopped = emitEvents(this, item, moveType, event, point,
|
stopped = emitEvents(this, item, moveType, event, point,
|
||||||
|
@ -1034,13 +1009,15 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
// 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 = clickItem === item
|
if (item) {
|
||||||
&& (Date.now() - clickTime < 300);
|
dblClick = item === clickItem
|
||||||
downItem = clickItem = item;
|
&& (Date.now() - clickTime < 300);
|
||||||
|
downItem = clickItem = item;
|
||||||
|
// Only start dragging if the mousedown event has not
|
||||||
|
// stopped propagation.
|
||||||
|
dragItem = !stopped && item;
|
||||||
|
}
|
||||||
downPoint = lastPoint = point;
|
downPoint = lastPoint = point;
|
||||||
// Only start dragging if the mousedown event has not
|
|
||||||
// stopped propagation.
|
|
||||||
dragItem = !stopped && item;
|
|
||||||
} else if (mouse.up) {
|
} else if (mouse.up) {
|
||||||
// Emulate click / doubleclick, but only on item, not view
|
// Emulate click / doubleclick, but only on item, not view
|
||||||
if (!stopped && item && item === downItem) {
|
if (!stopped && item && item === downItem) {
|
||||||
|
@ -1053,27 +1030,29 @@ new function() { // Injection scope for mouse events on the browser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastPoint = point;
|
lastPoint = point;
|
||||||
return called && !enforced;
|
// Now finally call the tool events, but filter mouse move events
|
||||||
|
// to only be fired if we're inside the view or if we just left it.
|
||||||
|
// Prevent default if at least one handler was called, and none of
|
||||||
|
// them enforces default, to prevent scrolling on touch devices.
|
||||||
|
if (tool && (mouse.move || inView || wasInView)
|
||||||
|
&& tool._handleEvent(type, event, point)
|
||||||
|
|| called && !enforced)
|
||||||
|
event.preventDefault();
|
||||||
|
// In the end we always call update(), which only updates the view
|
||||||
|
// if anything has changed in the above calls.
|
||||||
|
this.update();
|
||||||
},
|
},
|
||||||
|
|
||||||
_installEvent: function(type) {
|
_countItemEvent: function(type, sign) {
|
||||||
// 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
|
// change the event counters now according to itemEvents.
|
||||||
var counters = this._eventCounters;
|
var events = this._itemEvents;
|
||||||
for (var key in mouseFlags) {
|
for (var key in itemEvents) {
|
||||||
counters[key] = (counters[key] || 0)
|
events[key] = (events[key] || 0)
|
||||||
+ (mouseFlags[key][type] || 0);
|
+ (itemEvents[key][type] || 0) * sign;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_uninstallEvent: function(type) {
|
|
||||||
// If the view requires counting of installed mouse events,
|
|
||||||
// decrease the counters now according to mouseFlags
|
|
||||||
var counters = this._eventCounters;
|
|
||||||
for (var key in mouseFlags)
|
|
||||||
counters[key] -= mouseFlags[key][type] || 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
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