diff --git a/src/core/Callback.js b/src/core/Callback.js index a10c2098..2f83418c 100644 --- a/src/core/Callback.js +++ b/src/core/Callback.js @@ -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]; }; diff --git a/src/dom/DomEvent.js b/src/dom/DomEvent.js index d01db9f8..fd18162d 100644 --- a/src/dom/DomEvent.js +++ b/src/dom/DomEvent.js @@ -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(); } }; diff --git a/src/tool/Tool.js b/src/tool/Tool.js index 1ad7f1cf..2a803ce5 100644 --- a/src/tool/Tool.js +++ b/src/tool/Tool.js @@ -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; } /** diff --git a/src/ui/CanvasView.js b/src/ui/CanvasView.js index e9e3bb34..e89017aa 100644 --- a/src/ui/CanvasView.js +++ b/src/ui/CanvasView.js @@ -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; } }; }); diff --git a/src/ui/Event.js b/src/ui/Event.js index 3b4ff68a..0ad74981 100644 --- a/src/ui/Event.js +++ b/src/ui/Event.js @@ -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() { diff --git a/src/ui/View.js b/src/ui/View.js index eb5a3e46..bc225df1 100644 --- a/src/ui/View.js +++ b/src/ui/View.js @@ -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