diff --git a/src/core/Callback.js b/src/core/Callback.js index 29a4aecc..51307121 100644 --- a/src/core/Callback.js +++ b/src/core/Callback.js @@ -32,7 +32,7 @@ var Callback = { // See if this is the first handler that we're attaching, and // call install if defined. if (entry.install && handlers.length == 1) - entry.install.call(this); + entry.install.call(this, type); } return this; }, @@ -53,7 +53,7 @@ var Callback = { if (!func || (index = handlers.indexOf(func)) != -1 && handlers.length == 1) { if (entry.uninstall) - entry.uninstall.call(this); + entry.uninstall.call(this, type); delete this._handlers[type]; } else if (index != -1) { // Just remove this one handler diff --git a/src/item/Item.js b/src/item/Item.js index a99118f4..e60e4115 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -26,13 +26,58 @@ */ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ _events: new function() { + + // Flags defining which native events are required by which Paper events + // as required for counting amount of necessary natives events. + // The mapping is native -> virtual + var mouseFlags = { + mousedown: { + mousedown: 1, + mousedrag: 1, + click: 1, + doubleclick: 1 + }, + mouseup: { + mouseup: 1, + mousedrag: 1, + click: 1, + doubleclick: 1 + }, + mousemove: { + mousedrag: 1, + mousemove: 1, + mouseenter: 1, + mouseleave: 1 + } + }; + + // Entry for all mouse events in the _events list + var mouseEvent = { + install: function(type) { + var counters = this._project.view._eventCounters; + for (var key in mouseFlags) { + counters[key] = (counters[key] || 0) + + (mouseFlags[key][type] || 0); + } + }, + uninstall: function(type) { + var counters = this._project.view._eventCounters; + for (var key in mouseFlags) + counters[key] -= mouseFlags[key][type] || 0; + } + }; + var onFrameItems = []; function onFrame(event) { for (var i = 0, l = onFrameItems.length; i < l; i++) onFrameItems[i].fire('frame', event); } - return { + return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick', + 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'], + function(name) { + this[name] = mouseEvent; + }, { onFrame: { install: function() { if (!onFrameItems.length) @@ -45,7 +90,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ this._project.view.detach('frame', onFrame); } } - }; + }); }, initialize: function() { diff --git a/src/paper.js b/src/paper.js index c0d185ed..6170429e 100644 --- a/src/paper.js +++ b/src/paper.js @@ -114,6 +114,7 @@ var paper = new function() { /*#*/ include('ui/Event.js'); /*#*/ include('ui/KeyEvent.js'); /*#*/ include('ui/Key.js'); +/*#*/ include('ui/MouseEvent.js'); /*#*/ include('tool/ToolEvent.js'); /*#*/ include('tool/Tool.js'); diff --git a/src/ui/Event.js b/src/ui/Event.js index 7533d96d..ff80713b 100644 --- a/src/ui/Event.js +++ b/src/ui/Event.js @@ -25,15 +25,19 @@ var Event = this.Event = Base.extend(/** @lends Event# */{ // PORT: Add to Scriptographer preventDefault: function() { + this._prevented = true; DomEvent.preventDefault(this.event); + return this; }, stopPropagation: function() { + this._stopped = true; DomEvent.stopPropagation(this.event); + return this; }, stop: function() { - DomEvent.stop(this.event); + return this.stopPropagation().preventDefault(); }, // DOCS: Document Event#modifiers diff --git a/src/ui/MouseEvent.js b/src/ui/MouseEvent.js new file mode 100644 index 00000000..6e23b8e1 --- /dev/null +++ b/src/ui/MouseEvent.js @@ -0,0 +1,42 @@ +/* + * Paper.js + * + * This file is part of Paper.js, a JavaScript Vector Graphics Library, + * based on Scriptographer.org and designed to be largely API compatible. + * http://paperjs.org/ + * http://scriptographer.org/ + * + * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey + * http://lehni.org/ & http://jonathanpuckey.com/ + * + * Distributed under the MIT license. See LICENSE file for details. + * + * All rights reserved. + */ + +/** + * @name MouseEvent + * + * @extends Event + */ +var MouseEvent = this.MouseEvent = Event.extend(new function() { + return /** @lends MouseEvent# */{ + initialize: function(type, point, target, event) { + this.base(event); + this.type = type; + this.point = point; + this.target = target; + }, + + /** + * @return {String} A string representation of the key event. + */ + toString: function() { + return '{ type: ' + this.type + + ', point: ' + this.point + + ', target: ' + this.target + + ', modifiers: ' + this.getModifiers() + + ' }'; + } + }; +}); diff --git a/src/ui/View.js b/src/ui/View.js index 41f2ac87..47ecf3d6 100644 --- a/src/ui/View.js +++ b/src/ui/View.js @@ -173,6 +173,7 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{ this._context = this._canvas.getContext('2d'); this._matrix = new Matrix(); this._zoom = 1; + this._eventCounters = {}; // Make sure the first view is focused for keyboard input straight away if (!View._focused) View._focused = this; @@ -553,6 +554,23 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{ load: updateFocus }); + var hitOptions = { + fill: true, + stroke: true, + tolerance: 0 + }; + + function callEvent(item, event, bubble) { + var called = false; + while (item) { + called = item.fire(event.type, event) || called; + if (called && (!bubble || event._stopped)) + break; + item = item.getParent(); + } + return called; + } + return { _createHandlers: function() { var view = this; @@ -560,12 +578,25 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{ function mousedown(event) { // Tell the Key class which view should receive keyboard input. View._focused = view; - if (!(tool = view._scope.tool)) - return; curPoint = viewToProject(view, event); - if (tool.onHandleEvent('mousedown', curPoint, event)) - view.draw(true); dragging = true; + + var update = false; + // TODO: Move this to CanvasView soon! + if (view._eventCounters.mousedown) { + var hit = view._project.hitTest(curPoint, hitOptions); + if (hit && hit.item) { + update = callEvent(hit.item, new MouseEvent('mousedown', + curPoint, hit.item, event), false); + } + } + + if (tool = view._scope.tool) + update = tool.onHandleEvent('mousedown', curPoint, event) + || update; + + if (update) + view.draw(true); } return {