Implement key events on View.

Closes #896
This commit is contained in:
Jürg Lehni 2016-01-27 11:36:39 +01:00
parent 0b991cefdd
commit 849688833e
5 changed files with 63 additions and 40 deletions

View file

@ -91,8 +91,6 @@ var Key = new function() {
function handleKey(down, key, character, event) { function handleKey(down, key, character, event) {
var type = down ? 'keydown' : 'keyup', var type = down ? 'keydown' : 'keyup',
view = View._focused, view = View._focused,
scope = view && view.isVisible() && view._scope,
tool = scope && scope.tool,
name; name;
keyMap[key] = down; keyMap[key] = down;
// Link the key from keydown with the character form keypress, so keyup // Link the key from keydown with the character form keypress, so keyup
@ -129,11 +127,9 @@ var Key = new function() {
// A normal key, add it to metaFixMap if that's defined. // A normal key, add it to metaFixMap if that's defined.
metaFixMap[key] = character; metaFixMap[key] = character;
} }
if (tool && tool.responds(type)) { if (view) {
// Update global reference to this scope. view._handleKeyEvent(down ? 'keydown' : 'keyup', event, key,
paper = scope; character);
// Call the onKeyDown or onKeyUp handler if present
tool.emit(type, new KeyEvent(down, key, character, event));
} }
} }

View file

@ -23,9 +23,9 @@
var KeyEvent = Event.extend(/** @lends KeyEvent# */{ var KeyEvent = Event.extend(/** @lends KeyEvent# */{
_class: 'KeyEvent', _class: 'KeyEvent',
initialize: function KeyEvent(down, key, character, event) { initialize: function KeyEvent(type, event, key, character) {
Event.call(this, event); this.type = type;
this.type = down ? 'keydown' : 'keyup'; this.event = event;
this.key = key; this.key = key;
this.character = character; this.character = character;
}, },

View file

@ -27,8 +27,8 @@ var MouseEvent = Event.extend(/** @lends MouseEvent# */{
_class: 'MouseEvent', _class: 'MouseEvent',
initialize: function MouseEvent(type, event, point, target, delta) { initialize: function MouseEvent(type, event, point, target, delta) {
Event.call(this, event);
this.type = type; this.type = type;
this.event = event;
this.point = point; this.point = point;
this.target = target; this.target = target;
this.delta = delta; this.delta = delta;

View file

@ -283,7 +283,7 @@ var Tool = PaperScopeItem.extend(/** @lends Tool# */{
* *
* @return {@true if at least one event handler was called}. * @return {@true if at least one event handler was called}.
*/ */
_handleEvent: function(type, event, point, mouse) { _handleMouseEvent: function(type, event, point, mouse) {
// Update global reference to this scope. // Update global reference to this scope.
paper = this._scope; paper = this._scope;
// If there is no mousedrag event installed, fall back to mousemove, // If there is no mousedrag event installed, fall back to mousemove,

View file

@ -907,12 +907,11 @@ var View = Base.extend(Emitter, /** @lends View# */{
} }
} }
}, },
new function() { // Injection scope for mouse events on the browser new function() { // Injection scope for event handling on the browser
/** /**
* Native event handling, coordinate conversion, focus handling and * Native event handling, coordinate conversion, focus handling and
* delegation to view and tool objects. * delegation to view and tool objects.
*/ */
var prevFocus, var prevFocus,
tempFocus, tempFocus,
dragging = false, // mousedown that started on a view. dragging = false, // mousedown that started on a view.
@ -940,7 +939,7 @@ new function() { // Injection scope for mouse events on the browser
} }
function handleMouseMove(view, event, point) { function handleMouseMove(view, event, point) {
view._handleEvent('mousemove', event, point); view._handleMouseEvent('mousemove', event, point);
} }
// Touch handling inspired by Hammer.js // Touch handling inspired by Hammer.js
@ -1002,7 +1001,7 @@ new function() { // Injection scope for mouse events on the browser
var view = View._focused = getView(event); var view = View._focused = getView(event);
if (!dragging) { if (!dragging) {
dragging = true; dragging = true;
view._handleEvent('mousedown', event); view._handleMouseEvent('mousedown', event);
} }
}; };
@ -1048,7 +1047,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 && dragging) if (view && dragging)
view._handleEvent('mouseup', event); view._handleMouseEvent('mouseup', event);
mouseDown = dragging = false; mouseDown = dragging = false;
}; };
@ -1074,7 +1073,7 @@ new function() { // Injection scope for mouse events on the browser
}; };
// Returns true if event was stopped, false otherwise. // Returns true if event was stopped, false otherwise.
function emitEvent(obj, type, event, point, prevPoint, stopItem) { function emitMouseEvent(obj, type, event, point, prevPoint, stopItem) {
var target = obj, var target = obj,
mouseEvent; mouseEvent;
@ -1083,8 +1082,7 @@ new function() { // Injection scope for mouse events 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( mouseEvent = new MouseEvent(type, event, point, target,
type, event, point, target,
// Calculate delta if prevPoint was passed // Calculate delta if prevPoint was passed
prevPoint ? point.subtract(prevPoint) : null); prevPoint ? point.subtract(prevPoint) : null);
} }
@ -1111,27 +1109,27 @@ new function() { // Injection scope for mouse events on the browser
} }
// Returns true if event was stopped, false otherwise. // Returns true if event was stopped, false otherwise.
function emitEvents(view, item, type, event, point, prevPoint) { function emitMouseEvents(view, item, 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
// user is dragging. // user is dragging.
view._project.removeOn(type); view._project.removeOn(type);
// Set called to false, so it will reflect if the following calls to // Set called to false, so it will reflect if the following calls to
// emitEvent() have called a handler. // emitMouseEvent() have called a handler.
called = false; called = false;
// First handle the drag-item and its parents, through bubbling. // First handle the drag-item and its parents, through bubbling.
return (dragItem && emitEvent(dragItem, type, event, point, return (dragItem && emitMouseEvent(dragItem, type, event, point,
prevPoint) prevPoint)
// Next handle the hit-test item, if it's different from the drag // Next handle the hit-test item, if it's different from the drag
// item and not a descendant of it (in which case it would already // item and not a descendant of it (in which case it would already
// have received an event in the call above). Use fallbacks to // have received an event in the call above). Use fallbacks to
// translate mousedrag to mousemove, since drag is handled above. // translate mousedrag to mousemove, since drag is handled above.
|| item && item !== dragItem && !item.isDescendant(dragItem) || item && item !== dragItem && !item.isDescendant(dragItem)
&& emitEvent(item, fallbacks[type] || type, event, point, && emitMouseEvent(item, fallbacks[type] || type, event, point,
prevPoint, dragItem) prevPoint, dragItem)
// Lastly handle the move / drag on the view, if we're still here. // Lastly handle the move / drag on the view, if we're still here.
|| emitEvent(view, type, event, point, prevPoint)); || emitMouseEvent(view, type, event, point, prevPoint));
} }
/** /**
@ -1161,7 +1159,7 @@ new function() { // Injection scope for mouse events on the browser
}; };
/** /**
* Various variables required by #_handleEvent() * Various variables required by #_handleMouseEvent()
*/ */
var downPoint, var downPoint,
lastPoint, lastPoint,
@ -1178,9 +1176,10 @@ new function() { // Injection scope for mouse events on the browser
_viewEvents: viewEvents, _viewEvents: viewEvents,
/** /**
* Private method to handle view and item events. * Private method to handle mouse events, and delegate to items and
* tools.
*/ */
_handleEvent: function(type, event, point) { _handleMouseEvent: function(type, event, point) {
var handleItems = this._itemEvents[type], var handleItems = this._itemEvents[type],
tool = this._scope.tool, tool = this._scope.tool,
view = this; view = this;
@ -1192,8 +1191,8 @@ new function() { // Injection scope for mouse events on the browser
// If it's a native mousemove event but the mouse is down, and at // If it's a native mousemove event but the mouse is down, and at
// least one of the events responds to mousedrag, convert to it. // least one of the events responds to mousedrag, convert to it.
// NOTE: emitEvent(), as well as Tool#_handleEvent() fall back to // NOTE: emitMouseEvent(), as well as Tool#_handleMouseEvent() fall
// mousemove if the objects don't respond to mousedrag. // back to mousemove if the objects don't respond to mousedrag.
if (type === 'mousemove' && dragging && responds('mousedrag')) if (type === 'mousemove' && dragging && responds('mousedrag'))
type = 'mousedrag'; type = 'mousedrag';
if (!point) if (!point)
@ -1209,7 +1208,7 @@ new function() { // Injection scope for mouse events on the browser
}), }),
item = hit && hit.item || undefined, item = hit && hit.item || undefined,
// 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._handleEvent() shall be called after. // to decide if tool._handleMouseEvent() shall be called after.
handle = false, handle = false,
mouse = {}; mouse = {};
// Create a simple lookup object to quickly check for different // Create a simple lookup object to quickly check for different
@ -1225,15 +1224,15 @@ new function() { // Injection scope for mouse events on the browser
// Handle mouseenter / leave between items. // Handle mouseenter / leave between items.
if (item !== overItem) { if (item !== overItem) {
if (overItem) if (overItem)
emitEvent(overItem, 'mouseleave', event, point); emitMouseEvent(overItem, 'mouseleave', event, point);
if (item) if (item)
emitEvent(item, 'mouseenter', event, point); emitMouseEvent(item, 'mouseenter', event, point);
} }
overItem = item; overItem = item;
// Handle mouseenter / leave on the view. // Handle mouseenter / leave on the view.
if (wasInView ^ inView) { if (wasInView ^ inView) {
emitEvent(this, inView ? 'mouseenter' : 'mouseleave', event, emitMouseEvent(this, inView ? 'mouseenter' : 'mouseleave',
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.
} }
@ -1241,7 +1240,7 @@ new function() { // Injection scope for mouse events on the browser
// 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)) {
emitEvents(this, item, moveType, event, point, lastPoint); emitMouseEvents(this, item, moveType, event, point, lastPoint);
handle = true; handle = true;
} }
wasInView = inView; wasInView = inView;
@ -1249,7 +1248,7 @@ new function() { // Injection scope for mouse events 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) {
var stopped = emitEvents(this, item, type, event, point, var stopped = emitMouseEvents(this, item, type, event, point,
downPoint); 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
@ -1268,7 +1267,7 @@ new function() { // Injection scope for mouse events on the browser
// 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) {
clickTime = Date.now(); clickTime = Date.now();
emitEvent(item, dblClick ? 'doubleclick' : 'click', emitMouseEvent(item, dblClick ? 'doubleclick' : 'click',
event, point, downPoint); event, point, downPoint);
dblClick = false; dblClick = false;
} }
@ -1285,8 +1284,10 @@ new function() { // Injection scope for mouse events on the browser
// to only be fired if we're inside the view or if we just left it. // 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 // Prevent default if at least one handler was called, and none of
// them enforces default, to prevent scrolling on touch devices. // them enforces default, to prevent scrolling on touch devices.
if (handle && tool) if (handle && tool) {
called = tool._handleEvent(type, event, point, mouse) || called; called = tool._handleMouseEvent(type, event, point, mouse)
|| called;
}
// Now call preventDefault()`, if any of these conditions are met: // Now call preventDefault()`, if any of these conditions are met:
// - If any of the handlers were called, except for mousemove events // - If any of the handlers were called, except for mousemove events
@ -1299,6 +1300,32 @@ new function() { // Injection scope for mouse events on the browser
event.preventDefault(); event.preventDefault();
}, },
/**
* Private method to handle key events.
*/
_handleKeyEvent: function(type, event, key, character) {
var scope = this._scope,
tool = scope.tool,
keyEvent;
function emit(obj) {
if (obj.responds(type)) {
// Update global reference to this scope.
paper = scope;
// Only produce the event object if we really need it.
obj.emit(type, keyEvent = keyEvent
|| new KeyEvent(type, event, key, character));
}
}
if (this.isVisible()) {
// Call the onKeyDown or onKeyUp handler if present
emit(this);
if (tool && tool.responds(type))
emit(tool);
}
},
_countItemEvent: function(type, sign) { _countItemEvent: function(type, sign) {
// If the view requires counting of installed mouse events, // If the view requires counting of installed mouse events,
// change the event counters now according to itemEvents. // change the event counters now according to itemEvents.