mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-03-14 08:49:50 -04:00
Implement propper support for pointer events and MSPointer events.
Closes #406 and #336.
This commit is contained in:
parent
5f00c02e67
commit
9c552b1739
4 changed files with 132 additions and 74 deletions
|
@ -46,6 +46,24 @@ var DomElement = new function() {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles both getting and setting of vendor prefix values
|
||||||
|
function handlePrefix(el, name, set, value) {
|
||||||
|
var prefixes = ['webkit', 'moz', 'Moz', 'ms', 'o', ''],
|
||||||
|
suffix = name[0].toUpperCase() + name.substring(1);
|
||||||
|
for (var i = 0; i < 6; i++) {
|
||||||
|
var prefix = prefixes[i],
|
||||||
|
key = prefix ? prefix + suffix : name;
|
||||||
|
if (key in el) {
|
||||||
|
if (set) {
|
||||||
|
el[key] = value;
|
||||||
|
} else {
|
||||||
|
return el[key];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return /** @lends DomElement */{
|
return /** @lends DomElement */{
|
||||||
create: function(nodes, parent) {
|
create: function(nodes, parent) {
|
||||||
var isArray = Array.isArray(nodes),
|
var isArray = Array.isArray(nodes),
|
||||||
|
@ -203,13 +221,17 @@ var DomElement = new function() {
|
||||||
* Gets the given property from the element, trying out all browser
|
* Gets the given property from the element, trying out all browser
|
||||||
* prefix variants.
|
* prefix variants.
|
||||||
*/
|
*/
|
||||||
getPrefixValue: function(el, name) {
|
getPrefixed: function(el, name) {
|
||||||
var value = el[name],
|
return handlePrefix(el, name);
|
||||||
prefixes = ['webkit', 'moz', 'ms', 'o'],
|
},
|
||||||
suffix = name[0].toUpperCase() + name.substring(1);
|
|
||||||
for (var i = 0; i < 4 && value == null; i++)
|
setPrefixed: function(el, name, value) {
|
||||||
value = el[prefixes[i] + suffix];
|
if (typeof name === 'object') {
|
||||||
return value;
|
for (var key in name)
|
||||||
|
handlePrefix(el, key, true, name[key]);
|
||||||
|
} else {
|
||||||
|
handlePrefix(el, name, true, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,13 +17,21 @@
|
||||||
*/
|
*/
|
||||||
var DomEvent = /** @lends DomEvent */{
|
var DomEvent = /** @lends DomEvent */{
|
||||||
add: function(el, events) {
|
add: function(el, events) {
|
||||||
for (var type in events)
|
for (var type in events) {
|
||||||
el.addEventListener(type, events[type], false);
|
var func = events[type],
|
||||||
|
parts = type.split(/[\s,]+/g);
|
||||||
|
for (var i = 0, l = parts.length; i < l; i++)
|
||||||
|
el.addEventListener(parts[i], func, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: function(el, events) {
|
remove: function(el, events) {
|
||||||
for (var type in events)
|
for (var type in events) {
|
||||||
el.removeEventListener(type, events[type], false);
|
var func = events[type],
|
||||||
|
parts = type.split(/[\s,]+/g);
|
||||||
|
for (var i = 0, l = parts.length; i < l; i++)
|
||||||
|
el.removeEventListener(parts[i], func, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getPoint: function(event) {
|
getPoint: function(event) {
|
||||||
|
@ -59,8 +67,7 @@ var DomEvent = /** @lends DomEvent */{
|
||||||
};
|
};
|
||||||
|
|
||||||
DomEvent.requestAnimationFrame = new function() {
|
DomEvent.requestAnimationFrame = new function() {
|
||||||
var nativeRequest = DomElement.getPrefixValue(window,
|
var nativeRequest = DomElement.getPrefixed(window, 'requestAnimationFrame'),
|
||||||
'requestAnimationFrame'),
|
|
||||||
requested = false,
|
requested = false,
|
||||||
callbacks = [],
|
callbacks = [],
|
||||||
focused = true,
|
focused = true,
|
||||||
|
|
|
@ -51,7 +51,7 @@ var CanvasView = View.extend(/** @lends CanvasView# */{
|
||||||
// Hi-DPI Canvas support based on:
|
// Hi-DPI Canvas support based on:
|
||||||
// http://www.html5rocks.com/en/tutorials/canvas/hidpi/
|
// http://www.html5rocks.com/en/tutorials/canvas/hidpi/
|
||||||
var deviceRatio = window.devicePixelRatio || 1,
|
var deviceRatio = window.devicePixelRatio || 1,
|
||||||
backingStoreRatio = DomElement.getPrefixValue(this._context,
|
backingStoreRatio = DomElement.getPrefixed(this._context,
|
||||||
'backingStorePixelRatio') || 1;
|
'backingStorePixelRatio') || 1;
|
||||||
this._pixelRatio = deviceRatio / backingStoreRatio;
|
this._pixelRatio = deviceRatio / backingStoreRatio;
|
||||||
}
|
}
|
||||||
|
|
149
src/ui/View.js
149
src/ui/View.js
|
@ -38,7 +38,20 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
if (this._id == null)
|
if (this._id == null)
|
||||||
element.setAttribute('id', this._id = 'view-' + View._id++);
|
element.setAttribute('id', this._id = 'view-' + View._id++);
|
||||||
// Install event handlers
|
// Install event handlers
|
||||||
DomEvent.add(element, this._viewHandlers);
|
DomEvent.add(element, this._viewEvents);
|
||||||
|
// Borrowed from Hammer.js:
|
||||||
|
var none = 'none';
|
||||||
|
DomElement.setPrefixed(element.style, {
|
||||||
|
userSelect: none,
|
||||||
|
// This makes the element blocking in IE10+
|
||||||
|
// You could experiment with the value, see this issue:
|
||||||
|
// https://github.com/EightMedia/hammer.js/issues/241
|
||||||
|
touchAction: none,
|
||||||
|
touchCallout: none,
|
||||||
|
contentZooming: none,
|
||||||
|
userDrag: none,
|
||||||
|
tapHighlightColor: 'rgba(0,0,0,0)'
|
||||||
|
});
|
||||||
// If the element has the resize attribute, resize the it to fill the
|
// If the element has the resize attribute, resize the it to fill the
|
||||||
// window and resize it again whenever the user resizes the window.
|
// window and resize it again whenever the user resizes the window.
|
||||||
if (PaperScope.hasAttribute(element, 'resize')) {
|
if (PaperScope.hasAttribute(element, 'resize')) {
|
||||||
|
@ -48,7 +61,7 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
that = this;
|
that = this;
|
||||||
size = DomElement.getViewportBounds(element)
|
size = DomElement.getViewportBounds(element)
|
||||||
.getSize().subtract(offset);
|
.getSize().subtract(offset);
|
||||||
this._windowHandlers = {
|
this._windowEvents = {
|
||||||
resize: function() {
|
resize: function() {
|
||||||
// Only update element offset if it's not invisible, as
|
// Only update element offset if it's not invisible, as
|
||||||
// otherwise the offset would be wrong.
|
// otherwise the offset would be wrong.
|
||||||
|
@ -60,7 +73,7 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
.getSize().subtract(offset));
|
.getSize().subtract(offset));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
DomEvent.add(window, this._windowHandlers);
|
DomEvent.add(window, this._windowEvents);
|
||||||
} else {
|
} else {
|
||||||
// Try visible size first, since that will help handling previously
|
// Try visible size first, since that will help handling previously
|
||||||
// scaled canvases (e.g. when dealing with pixel-ratio)
|
// scaled canvases (e.g. when dealing with pixel-ratio)
|
||||||
|
@ -130,8 +143,8 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
this._project.view = null;
|
this._project.view = null;
|
||||||
/*#*/ if (__options.environment == 'browser') {
|
/*#*/ if (__options.environment == 'browser') {
|
||||||
// Uninstall event handlers again for this view.
|
// Uninstall event handlers again for this view.
|
||||||
DomEvent.remove(this._element, this._viewHandlers);
|
DomEvent.remove(this._element, this._viewEvents);
|
||||||
DomEvent.remove(window, this._windowHandlers);
|
DomEvent.remove(window, this._windowEvents);
|
||||||
/*#*/ } // __options.environment == 'browser'
|
/*#*/ } // __options.environment == 'browser'
|
||||||
this._element = this._project = null;
|
this._element = this._project = null;
|
||||||
// Remove all onFrame handlers.
|
// Remove all onFrame handlers.
|
||||||
|
@ -687,7 +700,68 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mousedown(event) {
|
function handleMouseMove(view, point, event) {
|
||||||
|
view._handleEvent('mousemove', point, event);
|
||||||
|
var tool = view._scope.tool;
|
||||||
|
if (tool) {
|
||||||
|
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
||||||
|
tool._handleEvent(dragging && tool.responds('mousedrag')
|
||||||
|
? 'mousedrag' : 'mousemove', point, event);
|
||||||
|
}
|
||||||
|
view.update();
|
||||||
|
return tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Touch handling inspired by Hammer.js
|
||||||
|
var navigator = window.navigator,
|
||||||
|
mousedown, mousemove, mouseup;
|
||||||
|
if (navigator.pointerEnabled || navigator.msPointerEnabled) {
|
||||||
|
// HTML5 / MS pointer events
|
||||||
|
mousedown = 'pointerdown MSPointerDown';
|
||||||
|
mousemove = 'pointermove MSPointerMove';
|
||||||
|
mouseup = 'pointerup pointercancel MSPointerUp MSPointerCancel';
|
||||||
|
} else {
|
||||||
|
mousedown = 'touchstart';
|
||||||
|
mousemove = 'touchmove';
|
||||||
|
mouseup = 'touchend touchcancel';
|
||||||
|
// Do not add mouse events on mobile and tablet devices
|
||||||
|
if (!('ontouchstart' in window && navigator.userAgent.match(
|
||||||
|
/mobile|tablet|ip(ad|hone|od)|android|silk/i))) {
|
||||||
|
// For non pointer events browsers and mixed browsers, like chrome
|
||||||
|
// on Windows8 touch laptop.
|
||||||
|
mousedown += ' mousedown';
|
||||||
|
mousemove += ' mousemove';
|
||||||
|
mouseup += ' mouseup';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var viewEvents = {
|
||||||
|
selectstart: function(event) {
|
||||||
|
// Only stop this even if we're dragging already, since otherwise no
|
||||||
|
// text whatsoever can be selected on the page.
|
||||||
|
if (dragging)
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var docEvents = {
|
||||||
|
mouseout: function(event) {
|
||||||
|
// When the moues leaves the document, fire one last mousemove
|
||||||
|
// event, to give items the change to receive a mouseleave, etc.
|
||||||
|
var view = View._focused,
|
||||||
|
target = DomEvent.getRelatedTarget(event);
|
||||||
|
if (view && (!target || target.nodeName === 'HTML'))
|
||||||
|
handleMouseMove(view, viewToProject(view, event), event);
|
||||||
|
},
|
||||||
|
|
||||||
|
scroll: updateFocus
|
||||||
|
};
|
||||||
|
|
||||||
|
// mousemove and mouseup events need to be installed on document, not the
|
||||||
|
// view element, since we want to catch the end of drag events even outside
|
||||||
|
// our view. Only the mousedown events are installed on the view, as defined
|
||||||
|
// by _viewEvents below.
|
||||||
|
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),
|
||||||
|
@ -701,29 +775,17 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
// In the end we always call update(), which only updates the view if
|
// In the end we always call update(), which only updates the view if
|
||||||
// anything has changed in the above calls.
|
// anything has changed in the above calls.
|
||||||
view.update();
|
view.update();
|
||||||
}
|
};
|
||||||
|
|
||||||
function handleMouseMove(view, point, event) {
|
docEvents[mousemove] = function(event) {
|
||||||
view._handleEvent('mousemove', point, event);
|
|
||||||
var tool = view._scope.tool;
|
|
||||||
if (tool) {
|
|
||||||
// If there's no onMouseDrag, fire onMouseMove while dragging.
|
|
||||||
tool._handleEvent(dragging && tool.responds('mousedrag')
|
|
||||||
? 'mousedrag' : 'mousemove', point, event);
|
|
||||||
}
|
|
||||||
view.update();
|
|
||||||
return tool;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mousemove(event) {
|
|
||||||
var view = View._focused;
|
var view = View._focused;
|
||||||
if (!dragging) {
|
if (!dragging) {
|
||||||
// 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);
|
||||||
if (target) {
|
if (target) {
|
||||||
// Temporarily focus this view without making it sticky, so
|
// Temporarily focus this view without making it sticky, so Key
|
||||||
// Key events are handled too during the mouse over
|
// events are handled too during the mouse over.
|
||||||
// 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)
|
||||||
|
@ -741,18 +803,9 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
if (dragging || view.getBounds().contains(point))
|
if (dragging || view.getBounds().contains(point))
|
||||||
tool = handleMouseMove(view, point, event);
|
tool = handleMouseMove(view, point, event);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
function mouseout(event) {
|
docEvents[mouseup] = function(event) {
|
||||||
// When the moues leaves the document, fire one last mousemove event,
|
|
||||||
// to give items the change to receive a mouseleave, etc.
|
|
||||||
var view = View._focused,
|
|
||||||
target = DomEvent.getRelatedTarget(event);
|
|
||||||
if (view && (!target || target.nodeName === 'HTML'))
|
|
||||||
handleMouseMove(view, viewToProject(view, event), event);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseup(event) {
|
|
||||||
var view = View._focused;
|
var view = View._focused;
|
||||||
if (!view || !dragging)
|
if (!view || !dragging)
|
||||||
return;
|
return;
|
||||||
|
@ -763,40 +816,16 @@ var View = Base.extend(Callback, /** @lends View# */{
|
||||||
if (tool)
|
if (tool)
|
||||||
tool._handleEvent('mouseup', point, event);
|
tool._handleEvent('mouseup', point, event);
|
||||||
view.update();
|
view.update();
|
||||||
}
|
};
|
||||||
|
|
||||||
function selectstart(event) {
|
DomEvent.add(document, docEvents);
|
||||||
// Only stop this even if we're dragging already, since otherwise no
|
|
||||||
// text whatsoever can be selected on the page.
|
|
||||||
if (dragging)
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// mousemove and mouseup events need to be installed on document, not the
|
|
||||||
// view element, since we want to catch the end of drag events even outside
|
|
||||||
// our view. Only the mousedown events are installed on the view, as handled
|
|
||||||
// by _createHandlers below.
|
|
||||||
|
|
||||||
DomEvent.add(document, {
|
|
||||||
mousemove: mousemove,
|
|
||||||
mouseout: mouseout,
|
|
||||||
mouseup: mouseup,
|
|
||||||
touchmove: mousemove,
|
|
||||||
touchend: mouseup,
|
|
||||||
selectstart: selectstart,
|
|
||||||
scroll: updateFocus
|
|
||||||
});
|
|
||||||
|
|
||||||
DomEvent.add(window, {
|
DomEvent.add(window, {
|
||||||
load: updateFocus
|
load: updateFocus
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_viewHandlers: {
|
_viewEvents: viewEvents,
|
||||||
mousedown: mousedown,
|
|
||||||
touchstart: mousedown,
|
|
||||||
selectstart: selectstart
|
|
||||||
},
|
|
||||||
|
|
||||||
// To be defined in subclasses
|
// To be defined in subclasses
|
||||||
_handleEvent: function(/* type, point, event */) {},
|
_handleEvent: function(/* type, point, event */) {},
|
||||||
|
|
Loading…
Reference in a new issue