diff --git a/Source/App/MouseHandler.js b/Source/App/MouseHandler.js index 3ed2e86..cb99d9b 100644 --- a/Source/App/MouseHandler.js +++ b/Source/App/MouseHandler.js @@ -14,7 +14,9 @@ authors: requires: - LibCanvas + - Mouse - App + - App.PointSearch provides: App.MouseHandler @@ -45,7 +47,7 @@ declare( 'LibCanvas.App.MouseHandler', { }; handler.search = handler.settings.get('search') || - new App.ElementsMouseSearch(); + new App.PointSearch(); this.events.forEach(function (type) { handler.mouse.events.add( type, function (e) { diff --git a/Source/App/ElementsMouseSearch.js b/Source/App/PointSearch.js similarity index 80% rename from Source/App/ElementsMouseSearch.js rename to Source/App/PointSearch.js index f58d0bb..c5b8095 100644 --- a/Source/App/ElementsMouseSearch.js +++ b/Source/App/PointSearch.js @@ -1,7 +1,7 @@ /* --- -name: "App.ElementsMouseSearch" +name: "App.PointSearch" description: "LibCanvas.App.ElementsMouseSearch" @@ -16,13 +16,13 @@ requires: - LibCanvas - App -provides: App.ElementsMouseSearch +provides: App.PointSearch ... */ -/** @class App.ElementsMouseSearch */ -declare( 'LibCanvas.App.ElementsMouseSearch', { +/** @class App.PointSearch */ +declare( 'LibCanvas.App.PointSearch', { initialize: function () { this.elements = []; @@ -51,4 +51,7 @@ declare( 'LibCanvas.App.ElementsMouseSearch', { return result; } -}); \ No newline at end of file +}); + +/** @deprecated */ +App.ElementsMouseSearch = App.PointSearch; \ No newline at end of file diff --git a/Source/Context/Context2D.js b/Source/Context/Context2D.js index d0a8da8..e96e8cf 100644 --- a/Source/Context/Context2D.js +++ b/Source/Context/Context2D.js @@ -21,6 +21,9 @@ requires: - Core.Canvas - Context.DrawImage - Context.Gradients + - Context.Path + - Context.Pixels + - Context.Text provides: Context2D diff --git a/Source/Core/Point.js b/Source/Core/Point.js index edf8c98..46cf46d 100644 --- a/Source/Core/Point.js +++ b/Source/Core/Point.js @@ -15,7 +15,6 @@ authors: requires: - LibCanvas - Geometry - - Utils.Math provides: Point diff --git a/Source/Plugins/ImageBuilder.js b/Source/Plugins/ImageBuilder.js index c35488f..66c7a63 100644 --- a/Source/Plugins/ImageBuilder.js +++ b/Source/Plugins/ImageBuilder.js @@ -17,7 +17,7 @@ requires: - LibCanvas - Point - - Rectangle + - Shapes.Rectangle ... */ diff --git a/libcanvas-full-compiled.js b/libcanvas-full-compiled.js index 7f591e6..e612cf6 100644 --- a/libcanvas-full-compiled.js +++ b/libcanvas-full-compiled.js @@ -1041,61 +1041,6 @@ declare( 'LibCanvas.App.Element', { /* --- -name: "App.ElementsMouseSearch" - -description: "LibCanvas.App.ElementsMouseSearch" - -license: - - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" - - "[MIT License](http://opensource.org/licenses/mit-license.php)" - -authors: - - "Shock " - -requires: - - LibCanvas - - App - -provides: App.ElementsMouseSearch - -... -*/ - -/** @class App.ElementsMouseSearch */ -declare( 'LibCanvas.App.ElementsMouseSearch', { - - initialize: function () { - this.elements = []; - }, - - add: function (elem) { - this.elements.push( elem ); - return this; - }, - - remove: function (elem) { - atom.core.eraseOne( this.elements, elem ); - return this; - }, - - removeAll: function () { - this.elements.length = 0; - return this; - }, - - findByPoint: function (point) { - var e = this.elements, i = e.length, result = []; - while (i--) if (e[i].isTriggerPoint( point )) { - result.push(e[i]); - } - return result; - } - -}); - -/* ---- - name: "App.Layer" description: "" @@ -1342,9 +1287,9 @@ declare( 'LibCanvas.App.Layer', { /* --- -name: "App.MouseHandler" +name: "Geometry" -description: "LibCanvas.App.MouseHandler" +description: "Base for such things as Point and Shape" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -1355,376 +1300,142 @@ authors: requires: - LibCanvas - - App -provides: App.MouseHandler +provides: Geometry ... */ -/** @class App.MouseHandler */ -declare( 'LibCanvas.App.MouseHandler', { +/** @class Geometry */ +var Geometry = declare( 'LibCanvas.Geometry', { + initialize : function () { + if (arguments.length) this.set.apply(this, arguments); + }, + cast: function (args) { + return this.constructor.castArguments(args); + } +}).own({ + invoke: declare.castArguments, + from : function (obj) { + return this(obj); + } +}); - events: 'down up move out dblclick contextmenu wheel'.split(' '), +/* +--- - /** @private */ - mouse: null, +name: "Point" - /** @constructs */ - initialize: function (settings) { - var handler = this; +description: "A X/Y point coordinates encapsulating class" - handler.settings = new Settings(settings); - handler.lastMouseMove = []; - handler.lastMouseDown = []; - handler.subscribers = []; +license: + - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" + - "[MIT License](http://opensource.org/licenses/mit-license.php)" - handler.app = handler.settings.get('app'); - handler.mouse = handler.settings.get('mouse'); - handler.compareFunction = function (left, right) { - return handler.app.zIndexCompare(left, right, true); - }; - handler.search = - handler.settings.get('search') || - new App.ElementsMouseSearch(); +authors: + - "Shock " - this.events.forEach(function (type) { - handler.mouse.events.add( type, function (e) { - handler.event(type, e); - }); - }); - }, +requires: + - LibCanvas + - Geometry - stop: function () { - this.stopped = true; - return this; - }, +provides: Point - start: function () { - this.stopped = false; - return this; - }, +... +*/ - subscribe : function (elem) { - if (this.subscribers.indexOf(elem) == -1) { - this.subscribers.push(elem); - this.search.add(elem); - } - return this; - }, +/** @class Point */ +var Point = LibCanvas.declare( 'LibCanvas.Point', 'Point', Geometry, { + x: 0, + y: 0, - unsubscribe : function (elem) { - var index = this.subscribers.indexOf(elem); - if (index != -1) { - this.subscribers.splice(index, 1); - atom.core.eraseOne(this.lastMouseDown, elem); - atom.core.eraseOne(this.lastMouseMove, elem); - this.search.remove(elem); + /** + * new Point(1, 1); + * new Point([1, 1]); + * new Point({x:1, y:1}); + * new Point(point); + * @constructs + * @param {Number} x + * @param {Number} y + * @returns {Point} + */ + set : function (x, y) { + if (arguments.length != 2) { + if (atom.core.isArrayLike(x)) { + y = x[1]; + x = x[0]; + } else if (x && x.x != null && x.y != null) { + y = x.y; + x = x.x; + } else { + throw new TypeError( 'Wrong Arguments In Point.Set' ); + } } - return this; - }, - unsubscribeAll: function () { - this.subscribers.length = 0; - this.search.removeAll(); + this.x = Number(x); + this.y = Number(y); return this; }, - - fall: function () { - this.falling = true; + /** @returns {Point} */ + move: function (distance, reverse) { + distance = this.cast(distance); + reverse = reverse ? -1 : 1; + this.x += distance.x * reverse; + this.y += distance.y * reverse; return this; }, + /** @returns {Point} */ + moveTo : function (point) { + return this.move(this.diff(this.cast(point))); + }, + /** @returns {Number} */ + angleTo : function (point) { + var diff = this.cast(point).diff(this); + return atom.math.normalizeAngle( Math.atan2(diff.y, diff.x) ); + }, + /** @returns {Number} */ + distanceTo : function (point) { + var diff = this.cast(point).diff(this); + return atom.math.hypotenuse(diff.x, diff.y); + }, + /** @returns {Boolean} */ + checkDistanceTo : function (point, distance, equals) { + var deltaX, deltaY, realDistanceSq, maxDistanceSq; - getOverElements: function () { - if (!this.mouse.inside) return []; + deltaX = Math.abs(this.x - point.x); + if (deltaX > distance) return false; - var - elements = this.search.findByPoint( this.mouse.point ), - i = elements.length; + deltaY = Math.abs(this.y - point.y); + if (deltaY > distance) return false; - while (i--) if (!elements[i].layer) { - this.unsubscribe(elements[i]); - elements.splice(i, 1); - } + realDistanceSq = deltaX*deltaX + deltaY*deltaY; + maxDistanceSq = distance*distance; - return elements.sort( this.compareFunction ); + return (realDistanceSq < maxDistanceSq) || + (equals && realDistanceSq == maxDistanceSq) }, - - /** @private */ - stopped: false, - - /** @private */ - falling: false, - - /** @private */ - checkFalling: function () { - var value = this.falling; - this.falling = false; - return value; + /** @returns {Point} */ + diff : function (point) { + return new this.constructor(point).move(this, true); }, + /** @returns {Point} */ + rotate : function (angle, pivot) { + pivot = pivot ? this.cast(pivot) : new this.constructor(0, 0); + if (this.equals(pivot)) return this; - /** @private */ - event: function (type, e) { - if (this.stopped) return; + var radius = pivot.distanceTo(this); + var sides = pivot.diff(this); + // TODO: check, maybe here should be "sides.y, sides.x" ? + var newAngle = Math.atan2(sides.x, sides.y) - angle; - var method = ['dblclick', 'contextmenu', 'wheel'].indexOf( type ) >= 0 - ? 'forceEvent' : 'parseEvent'; - - return this[method]( type, e ); + return this.moveTo({ + x : Math.sin(newAngle) * radius + pivot.x, + y : Math.cos(newAngle) * radius + pivot.y + }); }, - - /** @private */ - parseEvent: function (type, event) { - if (type == 'down') this.lastMouseDown.length = 0; - - var i, elem, - elements = this.getOverElements(), - stopped = false, - eventArgs = [event], - isChangeCoordEvent = (type == 'move' || type == 'out'); - - // В первую очередь - обрабатываем реальный mouseout с элементов - if (isChangeCoordEvent) { - this.informOut(eventArgs, elements); - } - - for (i = elements.length; i--;) { - elem = elements[i]; - // мышь над элементом, сообщаем о mousemove - // о mouseover, mousedown, click, если необходимо - if (!stopped) { - if (this.fireElem( type, elem, eventArgs )) { - stopped = true; - if (!isChangeCoordEvent) break; - } - // предыдущий элемент принял событие на себя - // необходимо сообщить остальным элементам под ним о mouseout - // Но только если это событие передвижения или выхода за границы холста - // а не активационные, как маусдаун или маусап - } else { - this.stoppedElem(elem, eventArgs); - } - } - - return stopped; - }, - - /** @private */ - informOut: function (eventArgs, elements) { - var - elem, - lastMove = this.lastMouseMove, - i = lastMove.length; - while (i--) { - elem = lastMove[i]; - if (elements.indexOf(elem) < 0) { - elem.events.fire( 'mouseout', eventArgs ); - lastMove.splice(i, 1); - } - } - }, - - /** @private */ - stoppedElem: function (elem, eventArgs) { - var - lastMove = this.lastMouseMove, - index = lastMove.indexOf(elem); - if (index > -1) { - elem.events.fire( 'mouseout', eventArgs ); - lastMove.splice(index, 1); - } - }, - - /** @private */ - fireElem: function (type, elem, eventArgs) { - var - lastDown = this.lastMouseDown, - lastMove = this.lastMouseMove; - - if (type == 'move') { - if (lastMove.indexOf(elem) < 0) { - elem.events.fire( 'mouseover', eventArgs ); - lastMove.push( elem ); - } - } else if (type == 'down') { - lastDown.push(elem); - // If mouseup on this elem and last mousedown was on this elem - click - } else if (type == 'up' && lastDown.indexOf(elem) > -1) { - elem.events.fire( 'click', eventArgs ); - } - elem.events.fire( 'mouse' + type, eventArgs ); - - return !this.checkFalling(); - }, - - /** @private */ - forceEvent: function (type, event) { - var - elements = this.getOverElements(), - i = elements.length; - while (i--) { - elements[i].events.fire( type, [ event ]); - if (!this.checkFalling()) { - break; - } - } - } - -}); - -/* ---- - -name: "Geometry" - -description: "Base for such things as Point and Shape" - -license: - - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" - - "[MIT License](http://opensource.org/licenses/mit-license.php)" - -authors: - - "Shock " - -requires: - - LibCanvas - -provides: Geometry - -... -*/ - -/** @class Geometry */ -var Geometry = declare( 'LibCanvas.Geometry', { - initialize : function () { - if (arguments.length) this.set.apply(this, arguments); - }, - cast: function (args) { - return this.constructor.castArguments(args); - } -}).own({ - invoke: declare.castArguments, - from : function (obj) { - return this(obj); - } -}); - -/* ---- - -name: "Point" - -description: "A X/Y point coordinates encapsulating class" - -license: - - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" - - "[MIT License](http://opensource.org/licenses/mit-license.php)" - -authors: - - "Shock " - -requires: - - LibCanvas - - Geometry - - Utils.Math - -provides: Point - -... -*/ - -/** @class Point */ -var Point = LibCanvas.declare( 'LibCanvas.Point', 'Point', Geometry, { - x: 0, - y: 0, - - /** - * new Point(1, 1); - * new Point([1, 1]); - * new Point({x:1, y:1}); - * new Point(point); - * @constructs - * @param {Number} x - * @param {Number} y - * @returns {Point} - */ - set : function (x, y) { - if (arguments.length != 2) { - if (atom.core.isArrayLike(x)) { - y = x[1]; - x = x[0]; - } else if (x && x.x != null && x.y != null) { - y = x.y; - x = x.x; - } else { - throw new TypeError( 'Wrong Arguments In Point.Set' ); - } - } - - this.x = Number(x); - this.y = Number(y); - return this; - }, - /** @returns {Point} */ - move: function (distance, reverse) { - distance = this.cast(distance); - reverse = reverse ? -1 : 1; - this.x += distance.x * reverse; - this.y += distance.y * reverse; - return this; - }, - /** @returns {Point} */ - moveTo : function (point) { - return this.move(this.diff(this.cast(point))); - }, - /** @returns {Number} */ - angleTo : function (point) { - var diff = this.cast(point).diff(this); - return atom.math.normalizeAngle( Math.atan2(diff.y, diff.x) ); - }, - /** @returns {Number} */ - distanceTo : function (point) { - var diff = this.cast(point).diff(this); - return atom.math.hypotenuse(diff.x, diff.y); - }, - /** @returns {Boolean} */ - checkDistanceTo : function (point, distance, equals) { - var deltaX, deltaY, realDistanceSq, maxDistanceSq; - - deltaX = Math.abs(this.x - point.x); - if (deltaX > distance) return false; - - deltaY = Math.abs(this.y - point.y); - if (deltaY > distance) return false; - - realDistanceSq = deltaX*deltaX + deltaY*deltaY; - maxDistanceSq = distance*distance; - - return (realDistanceSq < maxDistanceSq) || - (equals && realDistanceSq == maxDistanceSq) - }, - /** @returns {Point} */ - diff : function (point) { - return new this.constructor(point).move(this, true); - }, - /** @returns {Point} */ - rotate : function (angle, pivot) { - pivot = pivot ? this.cast(pivot) : new this.constructor(0, 0); - if (this.equals(pivot)) return this; - - var radius = pivot.distanceTo(this); - var sides = pivot.diff(this); - // TODO: check, maybe here should be "sides.y, sides.x" ? - var newAngle = Math.atan2(sides.x, sides.y) - angle; - - return this.moveTo({ - x : Math.sin(newAngle) * radius + pivot.x, - y : Math.cos(newAngle) * radius + pivot.y - }); - }, - /** @returns {Point} */ - scale : function (power, pivot) { - pivot = pivot ? this.cast(pivot) : new this.constructor(0, 0); + /** @returns {Point} */ + scale : function (power, pivot) { + pivot = pivot ? this.cast(pivot) : new this.constructor(0, 0); var diff = this.diff(pivot), isObject = typeof power == 'object'; return this.moveTo({ @@ -1854,9 +1565,9 @@ Point.shifts = atom.object.map({ /* --- -name: "Size" +name: "Mouse" -description: "" +description: "A mouse control abstraction class" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -1869,149 +1580,173 @@ requires: - LibCanvas - Point -provides: Size +provides: Mouse ... */ -/** @class Size */ -var Size = LibCanvas.declare( 'LibCanvas.Size', 'Size', Point, { - set: function method (size) { - if (typeof size == 'object' && size.width != null) { - this.x = Number(size.width); - this.y = Number(size.height); - - return this; - } - return method.previous.apply( this, arguments ); +/** @class Mouse */ +var Mouse = LibCanvas.declare( 'LibCanvas.Mouse', 'Mouse', { + /** @private */ + elem: null, + + /** @property {boolean} */ + inside: false, + /** @property {Point} */ + point: null, + /** @property {Point} */ + previous: null, + /** @property {Point} */ + delta: null, + /** @property {Events} */ + events: null, + + /** @private */ + mapping: { + click : 'click', + dblclick : 'dblclick', + contextmenu: 'contextmenu', + + mouseover : 'over', + mouseout : 'out', + mousedown : 'down', + mouseup : 'up', + mousemove : 'move', + + DOMMouseScroll: 'wheel', + mousewheel : 'wheel' }, - get width ( ) { return this.x }, - get height ( ) { return this.y }, - set width (w) { this.x = w }, - set height (h) { this.y = h }, + initialize : function (elem, offsetElem) { + this.bindMethods( 'onEvent' ); - /** @returns {object} */ - toObject: function () { - return { width: this.x, height: this.y }; - } -}); + if (elem == null) { + throw new TypeError('`elem` is undefined'); + } -/** @private */ -Size.from = function (object) { - if (object == null) return null; + this.elem = atom.dom(elem); + this.offsetElem = offsetElem ? atom.dom(offsetElem) : this.elem; - return object instanceof Size ? object : new Size(object); -}; + this.point = new Point(0, 0); + this.previous = new Point(0, 0); + this.delta = new Point(0, 0); + this.events = new Events(this); -/* ---- + this.listen(this.onEvent); + }, + /** @private */ + fire: function (name, e) { + this.events.fire(name, [e, this]); + return this; + }, + /** @private */ + onEvent: function (e) { + var + name = this.mapping[e.type], + fn = this.eventActions[name]; -name: "Shape" + if (fn) fn.call(this, e); -description: "Abstract class LibCanvas.Shape defines interface for drawable canvas objects" + this.fire(name, e); + }, + /** @private */ + getOffset: function (e) { + return this.constructor.getOffset(e, this.offsetElem); + }, + /** @private */ + set: function (e, inside) { + var point = this.getOffset(e); -license: - - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" - - "[MIT License](http://opensource.org/licenses/mit-license.php)" + this.previous.set( this.point ); + this.delta .set( this.previous.diff( point ) ); + this.point .set( point ); + this.inside = inside; + }, + /** @private */ + eventActions: { + wheel: function (e) { + this.constructor.addWheelDelta(e); + }, -authors: - - "Shock " + move: function (e) { + this.set(e, true); + }, -requires: - - LibCanvas - - Geometry - - Point + down: function (e) { + this.set(e, true); + }, -provides: Shape + over: function (e) { + if (this.checkEvent(e)) { + this.fire('enter', e); + } + }, -... -*/ + out: function (e) { + if (this.checkEvent(e)) { + this.set(e, false); + this.fire('leave', e); + } + } + }, + /** @private */ + checkEvent: function (e) { + var related = e.relatedTarget, elem = this.elem; -var shapeTestBuffer = function () { - if (!shapeTestBuffer.buffer) { - return shapeTestBuffer.buffer = LibCanvas.buffer(1, 1, true); + return related == null || ( + related && related != elem.first && !elem.contains(related) + ); + }, + /** @private */ + listen : function (callback) { + this.elem + .bind({ selectstart: false }) + .bind(atom.object.map( + this.mapping, atom.fn.lambda(callback) + )); } - return shapeTestBuffer.buffer; -}; +}).own({ + prevent: function (e) {e.preventDefault()}, + addWheelDelta: function (e) { + e.delta = + // IE, Opera, Chrome + e.wheelDelta ? e.wheelDelta > 0 ? 1 : -1 : + // Fx + e.detail ? e.detail < 0 ? 1 : -1 : null; -/** @class Shape */ -var Shape = declare( 'LibCanvas.Shape', Geometry, { - set : 'abstract', - hasPoint : 'abstract', - processPath: 'abstract', - draw : function (ctx, type) { - this.processPath(ctx)[type](); - return this; + return e; }, - // Методы ниже рассчитывают на то, что в фигуре есть точки from и to - getCoords : function () { - return this.from; + eventSource: function (e) { + return e.changedTouches ? e.changedTouches[0] : e; }, - /** @returns {LibCanvas.Shape} */ - grow: function (size) { - if (typeof size == 'number') { - size = new Point(size/2, size/2); - } else { - size = new Point(size.x/2, size.y/2); + expandEvent: function (e) { + var source = this.eventSource(e); + + if (e.pageX == null) { + e.pageX = source.pageX != null ? source.pageX : source.clientX + document.scrollLeft; + e.pageY = source.pageY != null ? source.pageY : source.clientY + document.scrollTop ; } - this.from.move(size, true); - this. to .move(size); - return this; - }, - get x () { return this.from.x }, - get y () { return this.from.y }, - set x (x) { - return this.move(new Point(x - this.x, 0)); - }, - set y (y) { - return this.move(new Point(0, y - this.y)); - }, - get bottomLeft () { - return new Point(this.from.x, this.to.y); - }, - get topRight() { - return new Point(this.to.x, this.from.y); - }, - get center() { - var from = this.from, to = this.to; - return new Point( (from.x + to.x) / 2, (from.y + to.y) / 2 ); - }, - getBoundingRectangle: function () { - return new Rectangle( this.from, this.to ); - }, - getCenter : function () { - return this.center; - }, - move : function (distance, reverse) { - this.from.move(distance, reverse); - this. to .move(distance, reverse); - return this; - }, - equals : function (shape, accuracy) { - return shape instanceof this.constructor && - shape.from.equals(this.from, accuracy) && - shape.to .equals(this.to , accuracy); - }, - clone : function () { - return new this.constructor(this.from.clone(), this.to.clone()); - }, - dumpPoint: function (point) { - return '[' + point.x + ', ' + point.y + ']'; + return e; }, - dump: function (shape) { - if (!shape) return this.toString(); - return '[shape '+shape+'(from'+this.dumpPoint(this.from)+', to'+this.dumpPoint(this.to)+')]'; + getOffset : function (e, element) { + var elementOffset = atom.dom(element || this.eventSource(e).target).offset(); + + this.expandEvent(e); + + return new Point( + e.pageX - elementOffset.x, + e.pageY - elementOffset.y + ); } }); /* --- -name: "Shapes.Rectangle" +name: "App.PointSearch" -description: "Provides rectangle as canvas object" +description: "LibCanvas.App.ElementsMouseSearch" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -2022,206 +1757,54 @@ authors: requires: - LibCanvas - - Point - - Shape + - App -provides: Shapes.Rectangle +provides: App.PointSearch ... */ -/** @class Rectangle */ -var MinusOnePoint = new Point(-1, -1); - -var Rectangle = LibCanvas.declare( 'LibCanvas.Shapes.Rectangle', 'Rectangle', Shape, { - set : function () { - var - center, - size, - a = atom.array.pickFrom(arguments), - first = a[0]; +/** @class App.PointSearch */ +declare( 'LibCanvas.App.PointSearch', { - this.from = null; - this.to = null; - - if (a.length == 4) { - this.from = new Point(a[0], a[1]); - this.to = new Point(a[0]+a[2], a[1]+a[3]); - } else if (a.length == 2) { - if ('width' in a[1] && 'height' in a[1]) { - this.set({ from: a[0], size: a[1] }); - } else { - this.from = Point.from(a[0]); - this.to = Point.from(a[1]); - } - } else if (first.center && first.size) { - center = Point.from(first.center); - size = Size.from(first.size); - - this.from = new Point(center.x - size.x/2, center.y - size.y/2); - this.to = new Point(center.x + size.x/2, center.y + size.y/2); - } else { - if (first.from) this.from = Point.from(first.from); - if (first.to ) this.to = Point.from(first.to); - - if (!this.from || !this.to && first.size) { - size = Size.from(first.size); - - if (this.from) { - this.to = new Point(this.from.x + size.x, this.from.y + size.y); - } else { - this.from = new Point(this.to.x - size.x, this.to.y - size.y); - } - } - } + initialize: function () { + this.elements = []; + }, + add: function (elem) { + this.elements.push( elem ); return this; }, - get width() { - return this.to.x - this.from.x; - }, - get height() { - return this.to.y - this.from.y; - }, - set width (width) { - this.to.x = this.from.x + width; - }, - set height (height) { - this.to.y = this.from.y + height; - }, - get size () { - return new Size( this.width, this.height ); - }, - set size (size) { - this.to.set(this.from.x + size.width, this.from.y + size.height); - }, - /** @returns {boolean} */ - hasPoint : function (point, padding) { - point = Point.from(point); - padding = padding || 0; - return point.x != null && point.y != null - && atom.number.between(point.x, Math.min(this.from.x, this.to.x) + padding, Math.max(this.from.x, this.to.x) - padding, 1) - && atom.number.between(point.y, Math.min(this.from.y, this.to.y) + padding, Math.max(this.from.y, this.to.y) - padding, 1); - }, - align: function (rect, sides) { - if (sides == null) sides = 'center middle'; - - var moveTo = this.from.clone(); - if (sides.indexOf('left') != -1) { - moveTo.x = rect.from.x; - } else if (sides.indexOf('center') != -1) { - moveTo.x = rect.from.x + (rect.width - this.width) / 2; - } else if (sides.indexOf('right') != -1) { - moveTo.x = rect.to.x - this.width; - } - - if (sides.indexOf('top') != -1) { - moveTo.y = rect.from.y; - } else if (sides.indexOf('middle') != -1) { - moveTo.y = rect.from.y + (rect.height - this.height) / 2; - } else if (sides.indexOf('bottom') != -1) { - moveTo.y = rect.to.y - this.height; - } - - return this.moveTo( moveTo ); - }, - /** @returns {LibCanvas.Shapes.Rectangle} */ - moveTo: function (rect) { - if (rect instanceof Point) { - this.move( this.from.diff(rect) ); - } else { - rect = Rectangle.from(rect); - this.from.moveTo(rect.from); - this. to.moveTo(rect.to); - } - return this; - }, - /** @returns {LibCanvas.Shapes.Rectangle} */ - draw : function (ctx, type) { - // fixed Opera bug - cant drawing rectangle with width or height below zero - ctx.original(type + 'Rect', [ - Math.min(this.from.x, this.to.x), - Math.min(this.from.y, this.to.y), - Math.abs(this.width ), - Math.abs(this.height) - ]); - return this; - }, - /** @returns {LibCanvas.Context2D} */ - processPath : function (ctx, noWrap) { - if (!noWrap) ctx.beginPath(); - ctx.ctx2d.rect( this.from.x, this.from.y, this.width, this.height ); - if (!noWrap) ctx.closePath(); - return ctx; - }, - /** @returns {boolean} */ - intersect : function (obj) { - if (obj.prototype != this.constructor) { - if (obj.getBoundingRectangle) { - obj = obj.getBoundingRectangle(); - } else return false; - } - return this.from.x < obj.to.x && this.to.x > obj.from.x - && this.from.y < obj.to.y && this.to.y > obj.from.y; - }, - getBoundingRectangle: function () { + remove: function (elem) { + atom.core.eraseOne( this.elements, elem ); return this; }, - /** @returns {LibCanvas.Point} */ - getRandomPoint : function (margin) { - margin = margin || 0; - return new Point( - atom.number.random(margin, this.width - margin), - atom.number.random(margin, this.height - margin) - ); - }, - /** @returns {LibCanvas.Shapes.Rectangle} */ - fillToPixel: function () { - var from = this.from, to = this.to, - point = function (side, round) { - return new Point( - Math[round](Math[side](from.x, to.x)), - Math[round](Math[side](from.y, to.y)) - ); - }; - return new Rectangle( - point( 'min', 'floor' ), - point( 'max', 'ceil' ) - ); - }, - /** @returns {LibCanvas.Shapes.Rectangle} */ - snapToPixel: function () { - this.from.snapToPixel(); - this.to.snapToPixel().move(MinusOnePoint); + removeAll: function () { + this.elements.length = 0; return this; }, - /** @returns {string} */ - dump: function method (name) { - return method.previous.call(this, name || 'Rectangle'); - }, - /** @returns {LibCanvas.Shapes.Polygon} */ - toPolygon: function () { - return new Polygon( - this.from.clone(), this.topRight, this.to.clone(), this.bottomLeft - ); + + findByPoint: function (point) { + var e = this.elements, i = e.length, result = []; + while (i--) if (e[i].isTriggerPoint( point )) { + result.push(e[i]); + } + return result; } -}); -/** @private */ -Rectangle.from = function (object) { - if (object == null) return null; +}); - return object instanceof Rectangle ? object : new Rectangle(object); -}; +/** @deprecated */ +App.ElementsMouseSearch = App.PointSearch; /* --- -name: "Shapes.Circle" +name: "App.MouseHandler" -description: "Provides circle as canvas object" +description: "LibCanvas.App.MouseHandler" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -2232,192 +1815,231 @@ authors: requires: - LibCanvas - - Point - - Shape + - Mouse + - App + - App.PointSearch -provides: Shapes.Circle +provides: App.MouseHandler ... */ -/** @class Circle */ -var Circle = LibCanvas.declare( 'LibCanvas.Shapes.Circle', 'Circle', Shape, { - set : function () { - var - center, radius, - a = atom.array.pickFrom(arguments); +/** @class App.MouseHandler */ +declare( 'LibCanvas.App.MouseHandler', { - if (a.length >= 3) { - center = new Point(a[0], a[1]); - radius = a[2]; - } else if (a.length == 2) { - center = Point.from(a[0]); - radius = a[1]; - } else { - a = a[0]; - radius = a.r == null ? a.radius : a.r; - if ('x' in a && 'y' in a) { - center = new Point(a.x, a.y); - } else if ('center' in a) { - center = Point.from(a.center); - } else if ('from' in a) { - center = new Point(a.from).move({ - x: this.radius, - y: this.radius - }); - } - } + events: 'down up move out dblclick contextmenu wheel'.split(' '), - this.center = center; - this.radius = radius; + /** @private */ + mouse: null, - if (center == null) throw new TypeError('center is null'); - if (radius == null) throw new TypeError('radius is null'); + /** @constructs */ + initialize: function (settings) { + var handler = this; + + handler.settings = new Settings(settings); + handler.lastMouseMove = []; + handler.lastMouseDown = []; + handler.subscribers = []; + + handler.app = handler.settings.get('app'); + handler.mouse = handler.settings.get('mouse'); + handler.compareFunction = function (left, right) { + return handler.app.zIndexCompare(left, right, true); + }; + handler.search = + handler.settings.get('search') || + new App.PointSearch(); + + this.events.forEach(function (type) { + handler.mouse.events.add( type, function (e) { + handler.event(type, e); + }); + }); }, - // we need accessors to redefine parent "get center" - get center ( ) { return this._center; }, - set center (c) { this._center = c; }, - grow: function (size) { - this.radius += size/2; + + stop: function () { + this.stopped = true; return this; }, - getCoords : function () { - return this.center; - }, - hasPoint : function (point) { - return this.center.checkDistanceTo(point, this.radius, true); + + start: function () { + this.stopped = false; + return this; }, - scale : function (factor, pivot) { - if (pivot) this.center.scale(factor, pivot); - this.radius *= factor; - return this; - }, - getCenter: function () { - return this.center; - }, - intersect : function (obj) { - if (obj instanceof this.constructor) { - return this.center.checkDistanceTo(obj.center, this.radius + obj.radius, true); - } else { - return this.getBoundingRectangle().intersect( obj ); + + subscribe : function (elem) { + if (this.subscribers.indexOf(elem) == -1) { + this.subscribers.push(elem); + this.search.add(elem); } - }, - move : function (distance, reverse) { - this.center.move(distance, reverse); return this; }, - processPath : function (ctx, noWrap) { - if (!noWrap) ctx.beginPath(); - if (this.radius) { - ctx.arc({ - circle : this, - angle : [0, Math.PI * 2] - }); + + unsubscribe : function (elem) { + var index = this.subscribers.indexOf(elem); + if (index != -1) { + this.subscribers.splice(index, 1); + atom.core.eraseOne(this.lastMouseDown, elem); + atom.core.eraseOne(this.lastMouseMove, elem); + this.search.remove(elem); } - if (!noWrap) ctx.closePath(); - return ctx; - }, - getBoundingRectangle: function () { - var r = this.radius, center = this.center; - return new Rectangle( - new Point(center.x - r, center.y - r), - new Point(center.x + r, center.y + r) - ); + return this; }, - clone : function () { - return new this.constructor(this.center.clone(), this.radius); + + unsubscribeAll: function () { + this.subscribers.length = 0; + this.search.removeAll(); + return this; }, - getPoints : function () { - return { center : this.center }; + + fall: function () { + this.falling = true; + return this; }, - equals : function (shape, accuracy) { - return shape instanceof this.shape && - shape.radius == this.radius && - shape.center.equals(this.center, accuracy); + + getOverElements: function () { + if (!this.mouse.inside) return []; + + var + elements = this.search.findByPoint( this.mouse.point ), + i = elements.length; + + while (i--) if (!elements[i].layer) { + this.unsubscribe(elements[i]); + elements.splice(i, 1); + } + + return elements.sort( this.compareFunction ); }, - dump: function () { - return '[shape Circle(center['+this.center.x+', '+this.center.y+'], '+this.radius+')]'; - } -}); -/** @private */ -Circle.from = function (object) { - if (object == null) return null; + /** @private */ + stopped: false, - return object instanceof Circle ? object : new Circle(object); -}; + /** @private */ + falling: false, + /** @private */ + checkFalling: function () { + var value = this.falling; + this.falling = false; + return value; + }, -/* ---- + /** @private */ + event: function (type, e) { + if (this.stopped) return; -name: "Core.Canvas" + var method = ['dblclick', 'contextmenu', 'wheel'].indexOf( type ) >= 0 + ? 'forceEvent' : 'parseEvent'; + + return this[method]( type, e ); + }, -description: "Provides some Canvas extensions" + /** @private */ + parseEvent: function (type, event) { + if (type == 'down') this.lastMouseDown.length = 0; -license: - - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" - - "[MIT License](http://opensource.org/licenses/mit-license.php)" + var i, elem, + elements = this.getOverElements(), + stopped = false, + eventArgs = [event], + isChangeCoordEvent = (type == 'move' || type == 'out'); -authors: - - "Shock " + // В первую очередь - обрабатываем реальный mouseout с элементов + if (isChangeCoordEvent) { + this.informOut(eventArgs, elements); + } -requires: - - LibCanvas + for (i = elements.length; i--;) { + elem = elements[i]; + // мышь над элементом, сообщаем о mousemove + // о mouseover, mousedown, click, если необходимо + if (!stopped) { + if (this.fireElem( type, elem, eventArgs )) { + stopped = true; + if (!isChangeCoordEvent) break; + } + // предыдущий элемент принял событие на себя + // необходимо сообщить остальным элементам под ним о mouseout + // Но только если это событие передвижения или выхода за границы холста + // а не активационные, как маусдаун или маусап + } else { + this.stoppedElem(elem, eventArgs); + } + } -provides: Core.Canvas + return stopped; + }, -... -*/ + /** @private */ + informOut: function (eventArgs, elements) { + var + elem, + lastMove = this.lastMouseMove, + i = lastMove.length; + while (i--) { + elem = lastMove[i]; + if (elements.indexOf(elem) < 0) { + elem.events.fire( 'mouseout', eventArgs ); + lastMove.splice(i, 1); + } + } + }, -atom.core.append(HTMLCanvasElement, -/** @lends HTMLCanvasElement */ -{ /** @private */ - _newContexts: {}, - /** @returns {HTMLCanvasElement} */ - addContext: function (name, ctx) { - this._newContexts[name] = ctx; - return this; + stoppedElem: function (elem, eventArgs) { + var + lastMove = this.lastMouseMove, + index = lastMove.indexOf(elem); + if (index > -1) { + elem.events.fire( 'mouseout', eventArgs ); + lastMove.splice(index, 1); + } }, - /** @returns {Context2D} */ - getContext: function (name) { - return this._newContexts[name] || null; - } -}); -atom.core.append(HTMLCanvasElement.prototype, -/** @lends HTMLCanvasElement.prototype */ -{ - getOriginalContext: HTMLCanvasElement.prototype.getContext, - /** @returns {Context2D} */ - getContext: function (type) { - if (!this.contextsList) { - this.contextsList = {}; + /** @private */ + fireElem: function (type, elem, eventArgs) { + var + lastDown = this.lastMouseDown, + lastMove = this.lastMouseMove; + + if (type == 'move') { + if (lastMove.indexOf(elem) < 0) { + elem.events.fire( 'mouseover', eventArgs ); + lastMove.push( elem ); + } + } else if (type == 'down') { + lastDown.push(elem); + // If mouseup on this elem and last mousedown was on this elem - click + } else if (type == 'up' && lastDown.indexOf(elem) > -1) { + elem.events.fire( 'click', eventArgs ); } + elem.events.fire( 'mouse' + type, eventArgs ); - if (!this.contextsList[type]) { - var ctx = HTMLCanvasElement.getContext(type); - if (ctx) { - ctx = new ctx(this); - } else try { - ctx = this.getOriginalContext.apply(this, arguments); - } catch (e) { - throw (!e.toString().match(/NS_ERROR_ILLEGAL_VALUE/)) ? e : - new TypeError('Wrong Context Type: «' + type + '»'); + return !this.checkFalling(); + }, + + /** @private */ + forceEvent: function (type, event) { + var + elements = this.getOverElements(), + i = elements.length; + while (i--) { + elements[i].events.fire( type, [ event ]); + if (!this.checkFalling()) { + break; } - this.contextsList[type] = ctx; } - return this.contextsList[type]; } + }); /* --- -name: "Context.DrawImage" +name: "Size" -description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" +description: "" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -2429,174 +2051,150 @@ authors: requires: - LibCanvas - Point - - Size - - Shapes.Rectangle - - Shapes.Circle -provides: Context.DrawImage +provides: Size ... */ -new function () { +/** @class Size */ +var Size = LibCanvas.declare( 'LibCanvas.Size', 'Size', Point, { + set: function method (size) { + if (typeof size == 'object' && size.width != null) { + this.x = Number(size.width); + this.y = Number(size.height); -var toPoint = Point.from, toRectangle = Rectangle.from; + return this; + } + return method.previous.apply( this, arguments ); + }, -/** @class LibCanvas.Context.DrawImage */ -LibCanvas.declare( 'LibCanvas.Context.DrawImage', { - initialize: function (context) { - this.context = context; - this.ctx2d = context.ctx2d; - }, - - drawImage: function (args) { - var a, center, from, draw, crop, scale, image, pivot, angle; + get width ( ) { return this.x }, + get height ( ) { return this.y }, + set width (w) { this.x = w }, + set height (h) { this.y = h }, - if (this.checkNonObject(args)) return; + /** @returns {object} */ + toObject: function () { + return { width: this.x, height: this.y }; + } +}); - a = args[0]; +/** @private */ +Size.from = function (object) { + if (object == null) return null; - image = a.image; - angle = a.angle; - scale = a.scale && toPoint(a.scale); - center = a.center && toPoint(a.center); - from = a.from && toPoint(a.from); - draw = a.draw && toRectangle(a.draw); - crop = a.crop && toRectangle(a.crop); + return object instanceof Size ? object : new Size(object); +}; - if (! atom.dom.isElement(image) ) throw new TypeError('Wrong image in Context.DrawImage'); - if (! (center || from || draw) ) throw new TypeError('Wrong arguments in Context.DrawImage'); +/* +--- - pivot = this.getTransformPivot( - angle, scale, image, - center, from, draw - ); +name: "Shape" - if (pivot) this.transform(pivot, angle, scale); - draw ? - this.drawRect (image, draw, crop , a.optimize) : - this.drawPoint(image, from, center, a.optimize); - if (pivot) this.ctx2d.restore(); +description: "Abstract class LibCanvas.Shape defines interface for drawable canvas objects" - return this.context; - }, +license: + - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" + - "[MIT License](http://opensource.org/licenses/mit-license.php)" - /** @private */ - run: function (array) { - this.ctx2d.drawImage.apply( this.ctx2d, array ); - }, - /** @private */ - transform: function (center, angle, scale) { - this.ctx2d.save(); - if (angle) this.context.rotate(angle, center); - if (scale) this.context.scale (scale, center); - }, - /** @private */ - checkNonObject: function (args) { - var image = args[0], length = args.length, target; - if (length > 2) { - this.run(args); - return true; - } - if (length == 2) { - target = args[1]; +authors: + - "Shock " - if (target instanceof Point) { - this.drawPoint(image, target); - return true; - } - if (target instanceof Rectangle) { - this.drawRect(image, target); - return true; - } +requires: + - LibCanvas + - Geometry + - Point - throw new Error('Unknown second argument in Context.DrawImage'); - } +provides: Shape - if (length == 0) { - throw new Error('Empty arguments in Context.DrawImage'); - } +... +*/ - if (atom.dom.isElement(image)) { - this.ctx2d.drawImage(image, 0, 0); - return true; - } +var shapeTestBuffer = function () { + if (!shapeTestBuffer.buffer) { + return shapeTestBuffer.buffer = LibCanvas.buffer(1, 1, true); + } + return shapeTestBuffer.buffer; +}; - return false; +/** @class Shape */ +var Shape = declare( 'LibCanvas.Shape', Geometry, { + set : 'abstract', + hasPoint : 'abstract', + processPath: 'abstract', + draw : function (ctx, type) { + this.processPath(ctx)[type](); + return this; }, - /** @private */ - drawPoint: function (image, from, center, optimize) { - var - point = center || from, - fromX = point.x, - fromY = point.y; - - if (center) { - fromX -= image.width / 2; - fromY -= image.height / 2; - } - - if (optimize) { - fromX = Math.round(fromX); - fromY = Math.round(fromY); - } - - this.ctx2d.drawImage(image, fromX, fromY); + // Методы ниже рассчитывают на то, что в фигуре есть точки from и to + getCoords : function () { + return this.from; }, - /** @private */ - drawRect: function (image, rect, crop, optimize) { - var deltaX, deltaY, fromX, fromY; - - if (crop) { - this.ctx2d.drawImage( image, - crop.from.x, crop.from.y, crop.width, crop.height, - rect.from.x, rect.from.y, rect.width, rect.height - ); - return; - } - - if (optimize) { - fromX = Math.round(rect.from.x); - fromY = Math.round(rect.from.y); - deltaX = Math.abs(rect.width - image.width ); - deltaY = Math.abs(rect.height - image.width ); - - if (deltaX < 1.1 && deltaY < 1.1) { - this.ctx2d.drawImage(image, fromX, fromY); - } else { - this.ctx2d.drawImage(image, fromX, fromY, - Math.round(rect.width), - Math.round(rect.height) - ); - } - return; + /** @returns {LibCanvas.Shape} */ + grow: function (size) { + if (typeof size == 'number') { + size = new Point(size/2, size/2); + } else { + size = new Point(size.x/2, size.y/2); } - this.ctx2d.drawImage( image, - rect.from.x, rect.from.y, - rect.width , rect.height - ); + this.from.move(size, true); + this. to .move(size); + return this; }, - /** @private */ - getTransformPivot: function (angle, scale, image, center, from, draw) { - if ( !angle && (!scale || (scale.x == 1 && scale.y == 1)) ) return null; - - if (center) return center; - if ( draw ) return draw.center; - - return new Point(from.x + image.width/2, from.y + image.height/2); + get x () { return this.from.x }, + get y () { return this.from.y }, + set x (x) { + return this.move(new Point(x - this.x, 0)); + }, + set y (y) { + return this.move(new Point(0, y - this.y)); + }, + get bottomLeft () { + return new Point(this.from.x, this.to.y); + }, + get topRight() { + return new Point(this.to.x, this.from.y); + }, + get center() { + var from = this.from, to = this.to; + return new Point( (from.x + to.x) / 2, (from.y + to.y) / 2 ); + }, + getBoundingRectangle: function () { + return new Rectangle( this.from, this.to ); + }, + getCenter : function () { + return this.center; + }, + move : function (distance, reverse) { + this.from.move(distance, reverse); + this. to .move(distance, reverse); + return this; + }, + equals : function (shape, accuracy) { + return shape instanceof this.constructor && + shape.from.equals(this.from, accuracy) && + shape.to .equals(this.to , accuracy); + }, + clone : function () { + return new this.constructor(this.from.clone(), this.to.clone()); + }, + dumpPoint: function (point) { + return '[' + point.x + ', ' + point.y + ']'; + }, + dump: function (shape) { + if (!shape) return this.toString(); + return '[shape '+shape+'(from'+this.dumpPoint(this.from)+', to'+this.dumpPoint(this.to)+')]'; } }); -}; - - /* --- -name: "Context.Gradients" +name: "Shapes.Rectangle" -description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" +description: "Provides rectangle as canvas object" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -2608,122 +2206,205 @@ authors: requires: - LibCanvas - Point - - Size - - Shapes.Rectangle - - Shapes.Circle + - Shape -provides: Context.Gradients +provides: Shapes.Rectangle ... */ -new function () { - -var toPoint = Point.from, toRectangle = Rectangle.from, toCircle = Circle.from; +/** @class Rectangle */ +var MinusOnePoint = new Point(-1, -1); +var Rectangle = LibCanvas.declare( 'LibCanvas.Shapes.Rectangle', 'Rectangle', Shape, { + set : function () { + var + center, + size, + a = atom.array.pickFrom(arguments), + first = a[0]; -var addColorStopSource = document - .createElement('canvas') - .getContext('2d') - .createLinearGradient(0,0,1,1) - .addColorStop; + this.from = null; + this.to = null; -var addColorStop = function (colors) { - if (typeof colors == 'object') { - for (var position in colors) if (colors.hasOwnProperty(position)) { - addColorStopSource.call( this, parseFloat(position), colors[position] ); - } - } else { - addColorStopSource.apply( this, arguments ); - } - return this; -}; + if (a.length == 4) { + this.from = new Point(a[0], a[1]); + this.to = new Point(a[0]+a[2], a[1]+a[3]); + } else if (a.length == 2) { + if ('width' in a[1] && 'height' in a[1]) { + this.set({ from: a[0], size: a[1] }); + } else { + this.from = Point.from(a[0]); + this.to = Point.from(a[1]); + } + } else if (first.center && first.size) { + center = Point.from(first.center); + size = Size.from(first.size); + this.from = new Point(center.x - size.x/2, center.y - size.y/2); + this.to = new Point(center.x + size.x/2, center.y + size.y/2); + } else { + if (first.from) this.from = Point.from(first.from); + if (first.to ) this.to = Point.from(first.to); -var fixGradient = function (grad) { - grad.addColorStop = addColorStop; - return grad; -}; + if (!this.from || !this.to && first.size) { + size = Size.from(first.size); -/** @class LibCanvas.Context.Gradients */ -LibCanvas.declare( 'LibCanvas.Context.Gradients', { - initialize: function (context) { - this.context = context; - this.ctx2d = context.ctx2d; + if (this.from) { + this.to = new Point(this.from.x + size.x, this.from.y + size.y); + } else { + this.from = new Point(this.to.x - size.x, this.to.y - size.y); + } + } + } + + return this; }, - /** @returns {CanvasGradient} */ - createGradient: function (from, to, colors) { - var gradient; - if ( from instanceof Rectangle ) { - colors = to; - gradient = this.createLinearGradient([ from ]); - } else if (from instanceof Circle) { - gradient = this.createRadialGradient([ from, to ]); - } else if (from instanceof Point) { - gradient = this.createLinearGradient([ from, to ]); - } else { - throw new Error('Unknown arguments in Context.Gradients.createGradient'); - } - if (typeof colors == 'object') gradient.addColorStop( colors ); - return gradient; + get width() { + return this.to.x - this.from.x; }, - /** @returns {CanvasGradient} */ - createRectangleGradient: function (rectangle, colors) { - rectangle = toRectangle( rectangle ); + get height() { + return this.to.y - this.from.y; + }, + set width (width) { + this.to.x = this.from.x + width; + }, + set height (height) { + this.to.y = this.from.y + height; + }, + get size () { + return new Size( this.width, this.height ); + }, + set size (size) { + this.to.set(this.from.x + size.width, this.from.y + size.height); + }, + /** @returns {boolean} */ + hasPoint : function (point, padding) { + point = Point.from(point); + padding = padding || 0; + return point.x != null && point.y != null + && atom.number.between(point.x, Math.min(this.from.x, this.to.x) + padding, Math.max(this.from.x, this.to.x) - padding, true) + && atom.number.between(point.y, Math.min(this.from.y, this.to.y) + padding, Math.max(this.from.y, this.to.y) - padding, true); + }, + align: function (rect, sides) { + if (sides == null) sides = 'center middle'; - var from = rectangle.from, line = new Line( rectangle.bottomLeft, rectangle.topRight ); + var moveTo = this.from.clone(); + if (sides.indexOf('left') != -1) { + moveTo.x = rect.from.x; + } else if (sides.indexOf('center') != -1) { + moveTo.x = rect.from.x + (rect.width - this.width) / 2; + } else if (sides.indexOf('right') != -1) { + moveTo.x = rect.to.x - this.width; + } - return this.createGradient( from, line.perpendicular(from).scale(2, from), colors ); - }, - /** @returns {CanvasGradient} */ - createLinearGradient : function (a) { - var from, to; - if (a.length != 4) { - if (a.length == 2) { - to = toPoint(a[0]); - from = toPoint(a[1]); - } else if (a.length == 1) { - to = toPoint(a[0].to); - from = toPoint(a[0].from); - } else { - throw new TypeError('Wrong arguments.length in the Context.createLinearGradient'); - } - a = [from.x, from.y, to.x, to.y]; + if (sides.indexOf('top') != -1) { + moveTo.y = rect.from.y; + } else if (sides.indexOf('middle') != -1) { + moveTo.y = rect.from.y + (rect.height - this.height) / 2; + } else if (sides.indexOf('bottom') != -1) { + moveTo.y = rect.to.y - this.height; } - return fixGradient( this.ctx2d.createLinearGradient.apply(this.ctx2d, a) ); + + return this.moveTo( moveTo ); }, - /** @returns {CanvasGradient} */ - createRadialGradient: function (a) { - var points, c1, c2, length = a.length; - if (length == 1 || length == 2) { - if (length == 2) { - c1 = toCircle( a[0] ); - c2 = toCircle( a[1] ); - } else { - c1 = toCircle( a.start ); - c2 = toCircle( a.end ); - } - points = [c1.center.x, c1.center.y, c1.radius, c2.center.x, c2.center.y, c2.radius]; - } else if (length == 6) { - points = a; + /** @returns {LibCanvas.Shapes.Rectangle} */ + moveTo: function (rect) { + if (rect instanceof Point) { + this.move( this.from.diff(rect) ); } else { - throw new TypeError('Wrong arguments.length in the Context.createRadialGradient'); + rect = Rectangle.from(rect); + this.from.moveTo(rect.from); + this. to.moveTo(rect.to); + } + return this; + }, + /** @returns {LibCanvas.Shapes.Rectangle} */ + draw : function (ctx, type) { + // fixed Opera bug - cant drawing rectangle with width or height below zero + ctx.original(type + 'Rect', [ + Math.min(this.from.x, this.to.x), + Math.min(this.from.y, this.to.y), + Math.abs(this.width ), + Math.abs(this.height) + ]); + return this; + }, + /** @returns {LibCanvas.Context2D} */ + processPath : function (ctx, noWrap) { + if (!noWrap) ctx.beginPath(); + ctx.ctx2d.rect( this.from.x, this.from.y, this.width, this.height ); + if (!noWrap) ctx.closePath(); + return ctx; + }, + /** @returns {boolean} */ + intersect : function (obj) { + if (obj.prototype != this.constructor) { + if (obj.getBoundingRectangle) { + obj = obj.getBoundingRectangle(); + } else return false; } + return this.from.x < obj.to.x && this.to.x > obj.from.x + && this.from.y < obj.to.y && this.to.y > obj.from.y; + }, + getBoundingRectangle: function () { + return this; + }, + /** @returns {LibCanvas.Point} */ + getRandomPoint : function (margin) { + margin = margin || 0; + return new Point( + atom.number.random(margin, this.width - margin), + atom.number.random(margin, this.height - margin) + ); + }, + /** @returns {LibCanvas.Shapes.Rectangle} */ + fillToPixel: function () { + var from = this.from, to = this.to, + point = function (side, round) { + return new Point( + Math[round](Math[side](from.x, to.x)), + Math[round](Math[side](from.y, to.y)) + ); + }; - return fixGradient( this.ctx2d.createRadialGradient.apply(this.ctx2d, points) ); + return new Rectangle( + point( 'min', 'floor' ), + point( 'max', 'ceil' ) + ); + }, + /** @returns {LibCanvas.Shapes.Rectangle} */ + snapToPixel: function () { + this.from.snapToPixel(); + this.to.snapToPixel().move(MinusOnePoint); + return this; + }, + /** @returns {string} */ + dump: function method (name) { + return method.previous.call(this, name || 'Rectangle'); + }, + /** @returns {LibCanvas.Shapes.Polygon} */ + toPolygon: function () { + return new Polygon( + this.from.clone(), this.topRight, this.to.clone(), this.bottomLeft + ); } }); -}; +/** @private */ +Rectangle.from = function (object) { + if (object == null) return null; + return object instanceof Rectangle ? object : new Rectangle(object); +}; /* --- -name: "Context2D" +name: "Shapes.Circle" -description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" +description: "Provides circle as canvas object" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -2735,545 +2416,368 @@ authors: requires: - LibCanvas - Point - - Size - - Shapes.Rectangle - - Shapes.Circle - - Core.Canvas - - Context.DrawImage - - Context.Gradients + - Shape -provides: Context2D +provides: Shapes.Circle ... */ -/** - * @class - * @name Context2D - * @name LibCanvas.Context2D - */ -var Context2D = function () { +/** @class Circle */ +var Circle = LibCanvas.declare( 'LibCanvas.Shapes.Circle', 'Circle', Shape, { + set : function () { + var + center, radius, + a = atom.array.pickFrom(arguments); -var office = { - all : function (type, style) { - this.save(); - if (style) this.set(type + 'Style', style); - this[type + 'Rect'](this.rectangle); - this.restore(); - return this; - }, - rect : function (func, args) { - var rect = office.makeRect.call(this, args); - return this.original(func, - [rect.from.x, rect.from.y, rect.width, rect.height]); + if (a.length >= 3) { + center = new Point(a[0], a[1]); + radius = a[2]; + } else if (a.length == 2) { + center = Point.from(a[0]); + radius = a[1]; + } else { + a = a[0]; + radius = a.r == null ? a.radius : a.r; + if ('x' in a && 'y' in a) { + center = new Point(a.x, a.y); + } else if ('center' in a) { + center = Point.from(a.center); + } else if ('from' in a) { + center = new Point(a.from).move({ + x: this.radius, + y: this.radius + }); + } + } + + this.center = center; + this.radius = radius; + + if (center == null) throw new TypeError('center is null'); + if (radius == null) throw new TypeError('radius is null'); }, - makeRect: function (args) { - return args.length ? Rectangle(args) : this.rectangle; + // we need accessors to redefine parent "get center" + get center ( ) { return this._center; }, + set center (c) { this._center = c; }, + grow: function (size) { + this.radius += size/2; + return this; }, - fillStroke : function (type, args) { - if (args.length >= 1 && args[0] instanceof Shape) { - if (args[1]) this.save().set(type + 'Style', args[1]); - args[0].draw(this, type); - if (args[1]) this.restore(); + getCoords : function () { + return this.center; + }, + hasPoint : function (point) { + return this.center.checkDistanceTo(point, this.radius, true); + }, + scale : function (factor, pivot) { + if (pivot) this.center.scale(factor, pivot); + this.radius *= factor; + return this; + }, + getCenter: function () { + return this.center; + }, + intersect : function (obj) { + if (obj instanceof this.constructor) { + return this.center.checkDistanceTo(obj.center, this.radius + obj.radius, true); } else { - if (args.length && args[0]) this.save().set(type + 'Style', args[0]); - this.original(type); - if (args.length && args[0]) this.restore(); + return this.getBoundingRectangle().intersect( obj ); } - + }, + move : function (distance, reverse) { + this.center.move(distance, reverse); return this; }, - originalPoint : function (func, args) { - var point = Point(args); - return this.original(func, [point.x, point.y]); + processPath : function (ctx, noWrap) { + if (!noWrap) ctx.beginPath(); + if (this.radius) { + ctx.arc({ + circle : this, + angle : [0, Math.PI * 2] + }); + } + if (!noWrap) ctx.closePath(); + return ctx; + }, + getBoundingRectangle: function () { + var r = this.radius, center = this.center; + return new Rectangle( + new Point(center.x - r, center.y - r), + new Point(center.x + r, center.y + r) + ); + }, + clone : function () { + return new this.constructor(this.center.clone(), this.radius); + }, + getPoints : function () { + return { center : this.center }; + }, + equals : function (shape, accuracy) { + return shape instanceof this.shape && + shape.radius == this.radius && + shape.center.equals(this.center, accuracy); + }, + dump: function () { + return '[shape Circle(center['+this.center.x+', '+this.center.y+'], '+this.radius+')]'; } +}); + +/** @private */ +Circle.from = function (object) { + if (object == null) return null; + + return object instanceof Circle ? object : new Circle(object); }; -/* In some Mobile browsers shadowY should be inverted (bug) */ -var shadowBug = function () { - // todo: use LibCanvas.buffer - var ctx = atom.dom - .create('canvas', { width: 15, height: 15 }) - .first.getContext( '2d' ); - ctx.shadowBlur = 1; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = -5; - ctx.shadowColor = 'green'; +/* +--- - ctx.fillRect( 0, 5, 5, 5 ); +name: "Core.Canvas" - // Color should contains green component to be correct (128 is correct value) - return ctx.getImageData(0, 0, 1, 1).data[1] < 64; +description: "Provides some Canvas extensions" -}(); +license: + - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" + - "[MIT License](http://opensource.org/licenses/mit-license.php)" -var constants = -/** @lends LibCanvas.Context2D */ -{ - COMPOSITE: { - SOURCE_OVER: 'source-over', - SOURCE_IN : 'source-in', - SOURCE_OUT : 'source-out', - SOURCE_ATOP: 'source-atop', +authors: + - "Shock " - DESTINATION_OVER: 'destination-over', - DESTINATION_IN : 'destination-in', - DESTINATION_OUT : 'destination-out', - DESTINATION_ATOP: 'destination-atop', +requires: + - LibCanvas - LIGHTER: 'lighter', - DARKER : 'darker', - COPY : 'copy', - XOR : 'xor' - }, +provides: Core.Canvas - LINE_CAP: { - BUTT : 'butt', - ROUND : 'round', - SQUARE: 'square' - }, +... +*/ - LINE_JOIN: { - ROUND: 'round', - BEVEL: 'bevel', - MITER: 'miter' +atom.core.append(HTMLCanvasElement, +/** @lends HTMLCanvasElement */ +{ + /** @private */ + _newContexts: {}, + /** @returns {HTMLCanvasElement} */ + addContext: function (name, ctx) { + this._newContexts[name] = ctx; + return this; }, + /** @returns {Context2D} */ + getContext: function (name) { + return this._newContexts[name] || null; + } +}); - TEXT_ALIGN: { - LEFT : 'left', - RIGHT : 'right', - CENTER: 'center', - START : 'start', - END : 'end' - }, +atom.core.append(HTMLCanvasElement.prototype, +/** @lends HTMLCanvasElement.prototype */ +{ + getOriginalContext: HTMLCanvasElement.prototype.getContext, + /** @returns {Context2D} */ + getContext: function (type) { + if (!this.contextsList) { + this.contextsList = {}; + } - TEXT_BASELINE: { - TOP : 'top', - HANGING : 'hanging', - MIDDLE : 'middle', - ALPHABETIC : 'alphabetic', - IDEOGRAPHIC: 'ideographic', - BOTTOM : 'bottom' - }, + if (!this.contextsList[type]) { + var ctx = HTMLCanvasElement.getContext(type); + if (ctx) { + ctx = new ctx(this); + } else try { + ctx = this.getOriginalContext.apply(this, arguments); + } catch (e) { + throw (!e.toString().match(/NS_ERROR_ILLEGAL_VALUE/)) ? e : + new TypeError('Wrong Context Type: «' + type + '»'); + } + this.contextsList[type] = ctx; + } + return this.contextsList[type]; + } +}); - SHADOW_BUG: shadowBug +/* +--- -}; +name: "Context.DrawImage" -var Context2D = LibCanvas.declare( 'LibCanvas.Context2D', 'Context2D', -/** - * @lends LibCanvas.Context2D.prototype - * @property {string} fillStyle - * @property {string} font - * @property {number} globalAlpha - * @property {string} globalCompositeOperation - * @property {string} lineCap - * @property {string} lineJoin - * @property {number} lineWidth - * @property {number} miterLimit - * @property {number} shadowOffsetX - * @property {number} shadowOffsetY - * @property {number} shadowBlur - * @property {string} shadowColor - * @property {string} strokeStyle - * @property {string} textAlign - * @property {string} textBaseline - */ -{ - initialize : function (canvas) { - if (canvas instanceof CanvasRenderingContext2D) { - this.ctx2d = canvas; - this.canvas = this.ctx2d.canvas; - } else { - this.canvas = canvas; - this.ctx2d = atom.core.isFunction(canvas.getOriginalContext) ? - canvas.getOriginalContext('2d') : - canvas.getContext('2d'); - } +description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" - this.helpers = { - image : new LibCanvas.Context.DrawImage(this), - gradients: new LibCanvas.Context.Gradients(this), - pixels : new LibCanvas.Context.Pixels (this), - text : new LibCanvas.Context.Text (this), - path : new LibCanvas.Context.Path (this) - }; - }, - get width () { return this.canvas.width; }, - get height() { return this.canvas.height; }, - set width (width) { this.canvas.width = width; }, - set height(height) { this.canvas.height = height;}, - - get size () { - return new Size(this.width, this.height); - }, - set size (size) { - size = Size.from(size); - this.width = size.width; - this.height = size.height; - }, - +license: + - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" + - "[MIT License](http://opensource.org/licenses/mit-license.php)" - get shadow () { - return [this.shadowOffsetX, this.shadowOffsetY, this.shadowBlur, this.shadowColor].join( ' ' ); +authors: + - "Shock " + +requires: + - LibCanvas + - Point + - Size + - Shapes.Rectangle + - Shapes.Circle + +provides: Context.DrawImage + +... +*/ + +new function () { + +var toPoint = Point.from, toRectangle = Rectangle.from; + +/** @class LibCanvas.Context.DrawImage */ +LibCanvas.declare( 'LibCanvas.Context.DrawImage', { + initialize: function (context) { + this.context = context; + this.ctx2d = context.ctx2d; }, - set shadow (value) { - value = value.split( ' ' ); - this.shadowOffsetX = value[0]; - this.shadowOffsetY = value[1]; - this.shadowBlur = value[2]; - this.shadowColor = value[3]; + drawImage: function (args) { + var a, center, from, draw, crop, scale, image, pivot, angle; + + if (this.checkNonObject(args)) return; + + a = args[0]; + + image = a.image; + angle = a.angle; + scale = a.scale && toPoint(a.scale); + center = a.center && toPoint(a.center); + from = a.from && toPoint(a.from); + draw = a.draw && toRectangle(a.draw); + crop = a.crop && toRectangle(a.crop); + + if (! atom.dom.isElement(image) ) throw new TypeError('Wrong image in Context.DrawImage'); + if (! (center || from || draw) ) throw new TypeError('Wrong arguments in Context.DrawImage'); + + pivot = this.getTransformPivot( + angle, scale, image, + center, from, draw + ); + + if (pivot) this.transform(pivot, angle, scale); + draw ? + this.drawRect (image, draw, crop , a.optimize) : + this.drawPoint(image, from, center, a.optimize); + if (pivot) this.ctx2d.restore(); + + return this.context; }, /** @private */ - safeSet: function (property, value) { - try { - this.ctx2d[property] = value; - } catch (e) { - throw TypeError('Exception while setting «' + property + '» to «' + value + '»: ' + e.message); - } + run: function (array) { + this.ctx2d.drawImage.apply( this.ctx2d, array ); }, - - set shadowOffsetY (value) { - if (shadowBug) value *= -1; - this.safeSet('shadowOffsetY', value); + /** @private */ + transform: function (center, angle, scale) { + this.ctx2d.save(); + if (angle) this.context.rotate(angle, center); + if (scale) this.context.scale (scale, center); }, + /** @private */ + checkNonObject: function (args) { + var image = args[0], length = args.length, target; + if (length > 2) { + this.run(args); + return true; + } + if (length == 2) { + target = args[1]; - set shadowBlur (value) { - if (shadowBug && value < 1) value = 1; - this.safeSet('shadowBlur', value); - }, + if (target instanceof Point) { + this.drawPoint(image, target); + return true; + } + if (target instanceof Rectangle) { + this.drawRect(image, target); + return true; + } - get shadowOffsetY () { - return this.ctx2d.shadowOffsetY; - }, + throw new Error('Unknown second argument in Context.DrawImage'); + } - get shadowBlur () { - return this.ctx2d.shadowBlur; - }, + if (length == 0) { + throw new Error('Empty arguments in Context.DrawImage'); + } - get opacity () { - return this.globalAlpha; - }, + if (atom.dom.isElement(image)) { + this.ctx2d.drawImage(image, 0, 0); + return true; + } - set opacity (value) { - this.globalAlpha = value; + return false; }, + /** @private */ + drawPoint: function (image, from, center, optimize) { + var + point = center || from, + fromX = point.x, + fromY = point.y; - _rectangle: null, - /** @returns {Rectangle} */ - get rectangle () { - var rect = this._rectangle; - if (!rect) { - this._rectangle = rect = new Rectangle(0, 0, this.width, this.height) - } else { - rect.size = this; + if (center) { + fromX -= image.width / 2; + fromY -= image.height / 2; } - return rect; - }, - /** @returns {Context2D} */ - original : function (method, args, returnResult) { - var result = this.ctx2d[method].apply(this.ctx2d, args || []); - return returnResult ? result: this; - }, - /** @returns {HTMLCanvasElement} */ - getClone : function (width, height) { - var resize = !!(width || height), canvas = this.canvas; - width = width || canvas.width; - height = height || canvas.height; - var args = [canvas, 0, 0]; - if (resize) args.push(width, height); + if (optimize) { + fromX = Math.round(fromX); + fromY = Math.round(fromY); + } - var clone = LibCanvas.buffer(width, height, true); - clone.ctx.original('drawImage', args); - return clone; + this.ctx2d.drawImage(image, fromX, fromY); }, + /** @private */ + drawRect: function (image, rect, crop, optimize) { + var deltaX, deltaY, fromX, fromY; - // Values - /** @returns {Context2D} */ - set : function (name, value) { - if (typeof name == 'object') { - for (var i in name) this[i] = name[i]; - } else this[name] = value; - return this; - }, - /** @returns {string} */ - get : function (name) { - return this[name]; - }, + if (crop) { + this.ctx2d.drawImage( image, + crop.from.x, crop.from.y, crop.width, crop.height, + rect.from.x, rect.from.y, rect.width, rect.height + ); + return; + } - // All - /** @returns {Context2D} */ - fillAll : function (style) { - return office.all.call(this, 'fill', style); - }, - /** @returns {Context2D} */ - strokeAll : function (style) { - return office.all.call(this, 'stroke', style); - }, - /** @returns {Context2D} */ - clearAll : function () { - return this.ctx2d.clearRect(0,0,this.canvas.width,this.canvas.height); - }, + if (optimize) { + fromX = Math.round(rect.from.x); + fromY = Math.round(rect.from.y); + deltaX = Math.abs(rect.width - image.width ); + deltaY = Math.abs(rect.height - image.width ); - // Save/Restore - /** @returns {Context2D} */ - save : function () { - this.ctx2d.save(); - return this; - }, - /** @returns {Context2D} */ - restore : function () { - this.ctx2d.restore(); - return this; - }, + if (deltaX < 1.1 && deltaY < 1.1) { + this.ctx2d.drawImage(image, fromX, fromY); + } else { + this.ctx2d.drawImage(image, fromX, fromY, + Math.round(rect.width), + Math.round(rect.height) + ); + } + return; + } - // Fill/Stroke - /** @returns {Context2D} */ - fill : function (shape) { - return office.fillStroke.call(this, 'fill', arguments); - }, - /** @returns {Context2D} */ - stroke : function (shape) { - return office.fillStroke.call(this, 'stroke', arguments); + this.ctx2d.drawImage( image, + rect.from.x, rect.from.y, + rect.width , rect.height + ); }, - /** @returns {Context2D} */ - clear: function (shape, stroke) { - return shape instanceof Shape && shape.constructor != Rectangle ? - this - .save() - .set({ globalCompositeOperation: Context2D.COMPOSITE.DESTINATION_OUT }) - [stroke ? 'stroke' : 'fill']( shape ) - .restore() : - this.clearRect( Rectangle.from(shape) ); - }, - - // Path - /** @returns {Context2D} */ - beginPath : function (moveTo) { - var ret = this.original('beginPath'); - arguments.length && this.moveTo.apply(this, arguments); - return ret; - }, - /** @returns {Context2D} */ - closePath : function () { - arguments.length && this.lineTo.apply(this, arguments); - return this.original('closePath'); - }, - /** @returns {Context2D} */ - moveTo : function (point) { - return office.originalPoint.call(this, 'moveTo', arguments); - }, - /** @returns {Context2D} */ - lineTo : function (point) { - return office.originalPoint.call(this, 'lineTo', arguments); - }, - /** @returns {Context2D} */ - arc : function (x, y, r, startAngle, endAngle, anticlockwise) { - return this.helpers.path.arc(arguments); - }, - /** @returns {Context2D} */ - arcTo : function () { - return this.helpers.path.arcTo(arguments); - }, - /** @returns {Context2D} */ - curveTo: function (curve) { - return this.helpers.path.curveTo(arguments); - }, - /** @returns {Context2D} */ - quadraticCurveTo : function () { - return this.helpers.path.quadraticCurveTo(arguments); - }, - /** @returns {Context2D} */ - bezierCurveTo : function () { - return this.helpers.path.bezierCurveTo(arguments); - }, - /** @returns {boolean} */ - isPointInPath : function (x, y) { - return this.helpers.path.isPointInPath(x, y); - }, - /** @returns {Context2D} */ - clip : function (shape) { - if (shape && typeof shape.processPath == 'function') { - shape.processPath(this); - } - return this.original('clip'); - }, - - // transformation - /** @returns {Context2D} */ - rotate : function (angle, pivot) { - if (angle) { - if (pivot) this.translate(pivot); - this.original('rotate', [angle]); - if (pivot) this.translate(pivot, true); - } - return this; - }, - /** @returns {Context2D} */ - translate : function (point, reverse) { - point = Point( - (arguments.length === 1 || typeof reverse === 'boolean') - ? point : arguments - ); - var multi = reverse === true ? -1 : 1; - this.original('translate', [point.x * multi, point.y * multi]); - return this; - }, - /** @returns {Context2D} */ - scale : function (power, pivot) { - if (typeof pivot == 'number') { - power = new Point(power, pivot); - pivot = null; - } else { - power = Point(power); - } - if (power.x != 1 || power.y != 1) { - if (pivot) this.translate(pivot); - this.original('scale', [power.x, power.y]); - if (pivot) this.translate(pivot, true); - } - return this; - }, - /** @returns {Context2D} */ - transform : function () { - // @todo Beauty arguments - return this.original('transform', arguments); - }, - /** @returns {Context2D} */ - setTransform : function () { - // @todo Beauty arguments - return this.original('setTransform', arguments); - }, - - // Rectangle - /** @returns {Context2D} */ - fillRect : function (rectangle) { - return office.rect.call(this, 'fillRect', arguments); - }, - /** @returns {Context2D} */ - strokeRect : function (rectangle) { - return office.rect.call(this, 'strokeRect', arguments); - }, - /** @returns {Context2D} */ - clearRect : function (rectangle) { - return office.rect.call(this, 'clearRect', arguments); - }, - - // === helpers.text === // - - /** @returns {Context2D} */ - fillText : function (text, x, y, maxWidth) { - return this.helpers.text.fillText(text, x, y, maxWidth); - }, - /** @returns {Context2D} */ - strokeText : function (text, x, y, maxWidth) { - return this.helpers.text.strokeText(text, x, y, maxWidth); - }, - /** @returns {object} */ - measureText : function (textToMeasure) { - return this.helpers.text.measureText(arguments); - }, - /** @returns {Context2D} */ - text : function (cfg) { - return this.helpers.text.text(cfg); - }, - - // === helpers.drawImage === // - - /** @returns {Context2D} */ - drawImage : function () { - return this.helpers.image.drawImage(arguments); - }, - - // === helpers.pixels === // - - /** @returns {CanvasPixelArray} */ - createImageData : function (w, h) { - return this.helpers.pixels.createImageData(w, h); - }, - /** @returns {Context2D} */ - putImageData : function () { - return this.helpers.pixels.putImageData(arguments); - }, - /** @returns {CanvasPixelArray} */ - getImageData : function (rectangle) { - return this.helpers.pixels.getImageData(arguments); - }, - getPixels : function (rectangle) { - return this.helpers.pixels.getPixels(arguments); - }, - getPixel: function (point) { - return this.helpers.pixels.getPixel(point); - }, - - // === helpers.gradients === // - - /** @returns {CanvasGradient} */ - createGradient: function (from, to, colors) { - return this.helpers.gradients.createGradient(from, to, colors); - }, - /** @returns {CanvasGradient} */ - createRectangleGradient: function (rectangle, colors) { - return this.helpers.gradients.createRectangleGradient(rectangle, colors); - }, - /** @returns {CanvasGradient} */ - createLinearGradient : function () { - return this.helpers.gradients.createLinearGradient(arguments); - }, - /** @returns {CanvasGradient} */ - createRadialGradient: function () { - return this.helpers.gradients.createRadialGradient(arguments); - }, - - // === etc === // + /** @private */ + getTransformPivot: function (angle, scale, image, center, from, draw) { + if ( !angle && (!scale || (scale.x == 1 && scale.y == 1)) ) return null; - /** @returns {CanvasPattern} */ - createPattern : function () { - return this.original('createPattern', arguments, true); - }, + if (center) return center; + if ( draw ) return draw.center; - drawWindow : function () { - return this.original('drawWindow', arguments); + return new Point(from.x + image.width/2, from.y + image.height/2); } - -}).own(constants); - - -[ 'fillStyle','font','globalAlpha','globalCompositeOperation','lineCap', - 'lineJoin','lineWidth','miterLimit','shadowOffsetX','shadowColor', - 'strokeStyle','textAlign','textBaseline' - // we'll set this values manually because of bug in Mobile Phones - // 'shadowOffsetY','shadowBlur' -].forEach(function (property) { - atom.accessors.define(Context2D.prototype, property, { - set: function (value) { - this.safeSet(property, value); - }, - get: function () { - return this.ctx2d[property]; - } - }) }); -Context2D.office = office; - -if (atom.core.isFunction(HTMLCanvasElement.addContext)) { - HTMLCanvasElement.addContext('2d-libcanvas', Context2D); -} - -return Context2D; - -}(); +}; /* --- -name: "Context.Path" +name: "Context.Gradients" description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" @@ -3288,30 +2792,157 @@ requires: - LibCanvas - Point - Size + - Shapes.Rectangle - Shapes.Circle -provides: Context.Path +provides: Context.Gradients ... */ new function () { -var toPoint = Point.from, toCircle = Circle.from; +var toPoint = Point.from, toRectangle = Rectangle.from, toCircle = Circle.from; -/** @class LibCanvas.Context.Path */ -LibCanvas.declare( 'LibCanvas.Context.Path', { - initialize: function (context) { - this.context = context; - this.ctx2d = context.ctx2d; - }, +var addColorStopSource = document + .createElement('canvas') + .getContext('2d') + .createLinearGradient(0,0,1,1) + .addColorStop; +var addColorStop = function (colors) { + if (typeof colors == 'object') { + for (var position in colors) if (colors.hasOwnProperty(position)) { + addColorStopSource.call( this, parseFloat(position), colors[position] ); + } + } else { + addColorStopSource.apply( this, arguments ); + } + return this; +}; - /** @returns {Context2D} */ - arc : function (a) { - if (a.length > 1) { +var fixGradient = function (grad) { + grad.addColorStop = addColorStop; + return grad; +}; + +/** @class LibCanvas.Context.Gradients */ +LibCanvas.declare( 'LibCanvas.Context.Gradients', { + initialize: function (context) { + this.context = context; + this.ctx2d = context.ctx2d; + }, + + /** @returns {CanvasGradient} */ + createGradient: function (from, to, colors) { + var gradient; + if ( from instanceof Rectangle ) { + colors = to; + gradient = this.createLinearGradient([ from ]); + } else if (from instanceof Circle) { + gradient = this.createRadialGradient([ from, to ]); + } else if (from instanceof Point) { + gradient = this.createLinearGradient([ from, to ]); + } else { + throw new Error('Unknown arguments in Context.Gradients.createGradient'); + } + if (typeof colors == 'object') gradient.addColorStop( colors ); + return gradient; + }, + /** @returns {CanvasGradient} */ + createRectangleGradient: function (rectangle, colors) { + rectangle = toRectangle( rectangle ); + + var from = rectangle.from, line = new Line( rectangle.bottomLeft, rectangle.topRight ); + + return this.createGradient( from, line.perpendicular(from).scale(2, from), colors ); + }, + /** @returns {CanvasGradient} */ + createLinearGradient : function (a) { + var from, to; + if (a.length != 4) { + if (a.length == 2) { + to = toPoint(a[0]); + from = toPoint(a[1]); + } else if (a.length == 1) { + to = toPoint(a[0].to); + from = toPoint(a[0].from); + } else { + throw new TypeError('Wrong arguments.length in the Context.createLinearGradient'); + } + a = [from.x, from.y, to.x, to.y]; + } + return fixGradient( this.ctx2d.createLinearGradient.apply(this.ctx2d, a) ); + }, + /** @returns {CanvasGradient} */ + createRadialGradient: function (a) { + var points, c1, c2, length = a.length; + if (length == 1 || length == 2) { + if (length == 2) { + c1 = toCircle( a[0] ); + c2 = toCircle( a[1] ); + } else { + c1 = toCircle( a.start ); + c2 = toCircle( a.end ); + } + points = [c1.center.x, c1.center.y, c1.radius, c2.center.x, c2.center.y, c2.radius]; + } else if (length == 6) { + points = a; + } else { + throw new TypeError('Wrong arguments.length in the Context.createRadialGradient'); + } + + return fixGradient( this.ctx2d.createRadialGradient.apply(this.ctx2d, points) ); + } +}); + +}; + + +/* +--- + +name: "Context.Path" + +description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" + +license: + - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" + - "[MIT License](http://opensource.org/licenses/mit-license.php)" + +authors: + - "Shock " + +requires: + - LibCanvas + - Point + - Size + - Shapes.Circle + +provides: Context.Path + +... +*/ + +new function () { + +var toPoint = Point.from, toCircle = Circle.from; + + +/** @class LibCanvas.Context.Path */ +LibCanvas.declare( 'LibCanvas.Context.Path', { + initialize: function (context) { + this.context = context; + this.ctx2d = context.ctx2d; + }, + + + /** @returns {Context2D} */ + arc : function (a) { + + if (a.length > 1) { return this.ctx2d.arc.apply(this.ctx2d, a); } else if (!a[0].circle) { throw new TypeError('Wrong arguments in CanvasContext.arc'); @@ -3737,9 +3368,9 @@ LibCanvas.declare( 'LibCanvas.Context.Text', { /* --- -name: "Mouse" +name: "Context2D" -description: "A mouse control abstraction class" +description: "LibCanvas.Context2D adds new canvas context '2d-libcanvas'" license: - "[GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)" @@ -3751,168 +3382,544 @@ authors: requires: - LibCanvas - Point + - Size + - Shapes.Rectangle + - Shapes.Circle + - Core.Canvas + - Context.DrawImage + - Context.Gradients + - Context.Path + - Context.Pixels + - Context.Text -provides: Mouse +provides: Context2D ... */ -/** @class Mouse */ -var Mouse = LibCanvas.declare( 'LibCanvas.Mouse', 'Mouse', { - /** @private */ - elem: null, +/** + * @class + * @name Context2D + * @name LibCanvas.Context2D + */ +var Context2D = function () { - /** @property {boolean} */ - inside: false, - /** @property {Point} */ - point: null, - /** @property {Point} */ - previous: null, - /** @property {Point} */ - delta: null, - /** @property {Events} */ - events: null, +var office = { + all : function (type, style) { + this.save(); + if (style) this.set(type + 'Style', style); + this[type + 'Rect'](this.rectangle); + this.restore(); + return this; + }, + rect : function (func, args) { + var rect = office.makeRect.call(this, args); + return this.original(func, + [rect.from.x, rect.from.y, rect.width, rect.height]); + }, + makeRect: function (args) { + return args.length ? Rectangle(args) : this.rectangle; + }, + fillStroke : function (type, args) { + if (args.length >= 1 && args[0] instanceof Shape) { + if (args[1]) this.save().set(type + 'Style', args[1]); + args[0].draw(this, type); + if (args[1]) this.restore(); + } else { + if (args.length && args[0]) this.save().set(type + 'Style', args[0]); + this.original(type); + if (args.length && args[0]) this.restore(); + } + + return this; + }, + originalPoint : function (func, args) { + var point = Point(args); + return this.original(func, [point.x, point.y]); + } +}; + +/* In some Mobile browsers shadowY should be inverted (bug) */ +var shadowBug = function () { + // todo: use LibCanvas.buffer + var ctx = atom.dom + .create('canvas', { width: 15, height: 15 }) + .first.getContext( '2d' ); + + ctx.shadowBlur = 1; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = -5; + ctx.shadowColor = 'green'; + + ctx.fillRect( 0, 5, 5, 5 ); + + // Color should contains green component to be correct (128 is correct value) + return ctx.getImageData(0, 0, 1, 1).data[1] < 64; + +}(); + +var constants = +/** @lends LibCanvas.Context2D */ +{ + COMPOSITE: { + SOURCE_OVER: 'source-over', + SOURCE_IN : 'source-in', + SOURCE_OUT : 'source-out', + SOURCE_ATOP: 'source-atop', + + DESTINATION_OVER: 'destination-over', + DESTINATION_IN : 'destination-in', + DESTINATION_OUT : 'destination-out', + DESTINATION_ATOP: 'destination-atop', + + LIGHTER: 'lighter', + DARKER : 'darker', + COPY : 'copy', + XOR : 'xor' + }, + + LINE_CAP: { + BUTT : 'butt', + ROUND : 'round', + SQUARE: 'square' + }, + + LINE_JOIN: { + ROUND: 'round', + BEVEL: 'bevel', + MITER: 'miter' + }, + + TEXT_ALIGN: { + LEFT : 'left', + RIGHT : 'right', + CENTER: 'center', + START : 'start', + END : 'end' + }, + + TEXT_BASELINE: { + TOP : 'top', + HANGING : 'hanging', + MIDDLE : 'middle', + ALPHABETIC : 'alphabetic', + IDEOGRAPHIC: 'ideographic', + BOTTOM : 'bottom' + }, + + SHADOW_BUG: shadowBug + +}; + +var Context2D = LibCanvas.declare( 'LibCanvas.Context2D', 'Context2D', +/** + * @lends LibCanvas.Context2D.prototype + * @property {string} fillStyle + * @property {string} font + * @property {number} globalAlpha + * @property {string} globalCompositeOperation + * @property {string} lineCap + * @property {string} lineJoin + * @property {number} lineWidth + * @property {number} miterLimit + * @property {number} shadowOffsetX + * @property {number} shadowOffsetY + * @property {number} shadowBlur + * @property {string} shadowColor + * @property {string} strokeStyle + * @property {string} textAlign + * @property {string} textBaseline + */ +{ + initialize : function (canvas) { + if (canvas instanceof CanvasRenderingContext2D) { + this.ctx2d = canvas; + this.canvas = this.ctx2d.canvas; + } else { + this.canvas = canvas; + this.ctx2d = atom.core.isFunction(canvas.getOriginalContext) ? + canvas.getOriginalContext('2d') : + canvas.getContext('2d'); + } + + this.helpers = { + image : new LibCanvas.Context.DrawImage(this), + gradients: new LibCanvas.Context.Gradients(this), + pixels : new LibCanvas.Context.Pixels (this), + text : new LibCanvas.Context.Text (this), + path : new LibCanvas.Context.Path (this) + }; + }, + get width () { return this.canvas.width; }, + get height() { return this.canvas.height; }, + set width (width) { this.canvas.width = width; }, + set height(height) { this.canvas.height = height;}, + + get size () { + return new Size(this.width, this.height); + }, + set size (size) { + size = Size.from(size); + this.width = size.width; + this.height = size.height; + }, + + + get shadow () { + return [this.shadowOffsetX, this.shadowOffsetY, this.shadowBlur, this.shadowColor].join( ' ' ); + }, + + set shadow (value) { + value = value.split( ' ' ); + this.shadowOffsetX = value[0]; + this.shadowOffsetY = value[1]; + this.shadowBlur = value[2]; + this.shadowColor = value[3]; + }, /** @private */ - mapping: { - click : 'click', - dblclick : 'dblclick', - contextmenu: 'contextmenu', + safeSet: function (property, value) { + try { + this.ctx2d[property] = value; + } catch (e) { + throw TypeError('Exception while setting «' + property + '» to «' + value + '»: ' + e.message); + } + }, - mouseover : 'over', - mouseout : 'out', - mousedown : 'down', - mouseup : 'up', - mousemove : 'move', + set shadowOffsetY (value) { + if (shadowBug) value *= -1; + this.safeSet('shadowOffsetY', value); + }, - DOMMouseScroll: 'wheel', - mousewheel : 'wheel' + set shadowBlur (value) { + if (shadowBug && value < 1) value = 1; + this.safeSet('shadowBlur', value); + }, + + get shadowOffsetY () { + return this.ctx2d.shadowOffsetY; + }, + + get shadowBlur () { + return this.ctx2d.shadowBlur; + }, + + get opacity () { + return this.globalAlpha; + }, + + set opacity (value) { + this.globalAlpha = value; + }, + + _rectangle: null, + /** @returns {Rectangle} */ + get rectangle () { + var rect = this._rectangle; + if (!rect) { + this._rectangle = rect = new Rectangle(0, 0, this.width, this.height) + } else { + rect.size = this; + } + return rect; + }, + /** @returns {Context2D} */ + original : function (method, args, returnResult) { + var result = this.ctx2d[method].apply(this.ctx2d, args || []); + return returnResult ? result: this; + }, + /** @returns {HTMLCanvasElement} */ + getClone : function (width, height) { + var resize = !!(width || height), canvas = this.canvas; + width = width || canvas.width; + height = height || canvas.height; + + var args = [canvas, 0, 0]; + if (resize) args.push(width, height); + + var clone = LibCanvas.buffer(width, height, true); + clone.ctx.original('drawImage', args); + return clone; + }, + + // Values + /** @returns {Context2D} */ + set : function (name, value) { + if (typeof name == 'object') { + for (var i in name) this[i] = name[i]; + } else this[name] = value; + return this; + }, + /** @returns {string} */ + get : function (name) { + return this[name]; + }, + + // All + /** @returns {Context2D} */ + fillAll : function (style) { + return office.all.call(this, 'fill', style); + }, + /** @returns {Context2D} */ + strokeAll : function (style) { + return office.all.call(this, 'stroke', style); + }, + /** @returns {Context2D} */ + clearAll : function () { + return this.ctx2d.clearRect(0,0,this.canvas.width,this.canvas.height); + }, + + // Save/Restore + /** @returns {Context2D} */ + save : function () { + this.ctx2d.save(); + return this; + }, + /** @returns {Context2D} */ + restore : function () { + this.ctx2d.restore(); + return this; + }, + + // Fill/Stroke + /** @returns {Context2D} */ + fill : function (shape) { + return office.fillStroke.call(this, 'fill', arguments); + }, + /** @returns {Context2D} */ + stroke : function (shape) { + return office.fillStroke.call(this, 'stroke', arguments); + }, + /** @returns {Context2D} */ + clear: function (shape, stroke) { + return shape instanceof Shape && shape.constructor != Rectangle ? + this + .save() + .set({ globalCompositeOperation: Context2D.COMPOSITE.DESTINATION_OUT }) + [stroke ? 'stroke' : 'fill']( shape ) + .restore() : + this.clearRect( Rectangle.from(shape) ); + }, + + // Path + /** @returns {Context2D} */ + beginPath : function (moveTo) { + var ret = this.original('beginPath'); + arguments.length && this.moveTo.apply(this, arguments); + return ret; + }, + /** @returns {Context2D} */ + closePath : function () { + arguments.length && this.lineTo.apply(this, arguments); + return this.original('closePath'); + }, + /** @returns {Context2D} */ + moveTo : function (point) { + return office.originalPoint.call(this, 'moveTo', arguments); + }, + /** @returns {Context2D} */ + lineTo : function (point) { + return office.originalPoint.call(this, 'lineTo', arguments); + }, + /** @returns {Context2D} */ + arc : function (x, y, r, startAngle, endAngle, anticlockwise) { + return this.helpers.path.arc(arguments); + }, + /** @returns {Context2D} */ + arcTo : function () { + return this.helpers.path.arcTo(arguments); + }, + /** @returns {Context2D} */ + curveTo: function (curve) { + return this.helpers.path.curveTo(arguments); + }, + /** @returns {Context2D} */ + quadraticCurveTo : function () { + return this.helpers.path.quadraticCurveTo(arguments); }, - - initialize : function (elem, offsetElem) { - this.bindMethods( 'onEvent' ); - - if (elem == null) { - throw new TypeError('`elem` is undefined'); + /** @returns {Context2D} */ + bezierCurveTo : function () { + return this.helpers.path.bezierCurveTo(arguments); + }, + /** @returns {boolean} */ + isPointInPath : function (x, y) { + return this.helpers.path.isPointInPath(x, y); + }, + /** @returns {Context2D} */ + clip : function (shape) { + if (shape && typeof shape.processPath == 'function') { + shape.processPath(this); } + return this.original('clip'); + }, - this.elem = atom.dom(elem); - this.offsetElem = offsetElem ? atom.dom(offsetElem) : this.elem; - - this.point = new Point(0, 0); - this.previous = new Point(0, 0); - this.delta = new Point(0, 0); - this.events = new Events(this); - - this.listen(this.onEvent); + // transformation + /** @returns {Context2D} */ + rotate : function (angle, pivot) { + if (angle) { + if (pivot) this.translate(pivot); + this.original('rotate', [angle]); + if (pivot) this.translate(pivot, true); + } + return this; }, - /** @private */ - fire: function (name, e) { - this.events.fire(name, [e, this]); + /** @returns {Context2D} */ + translate : function (point, reverse) { + point = Point( + (arguments.length === 1 || typeof reverse === 'boolean') + ? point : arguments + ); + var multi = reverse === true ? -1 : 1; + this.original('translate', [point.x * multi, point.y * multi]); return this; }, - /** @private */ - onEvent: function (e) { - var - name = this.mapping[e.type], - fn = this.eventActions[name]; - - if (fn) fn.call(this, e); - - this.fire(name, e); + /** @returns {Context2D} */ + scale : function (power, pivot) { + if (typeof pivot == 'number') { + power = new Point(power, pivot); + pivot = null; + } else { + power = Point(power); + } + if (power.x != 1 || power.y != 1) { + if (pivot) this.translate(pivot); + this.original('scale', [power.x, power.y]); + if (pivot) this.translate(pivot, true); + } + return this; }, - /** @private */ - getOffset: function (e) { - return this.constructor.getOffset(e, this.offsetElem); + /** @returns {Context2D} */ + transform : function () { + // @todo Beauty arguments + return this.original('transform', arguments); + }, + /** @returns {Context2D} */ + setTransform : function () { + // @todo Beauty arguments + return this.original('setTransform', arguments); }, - /** @private */ - set: function (e, inside) { - var point = this.getOffset(e); - this.previous.set( this.point ); - this.delta .set( this.previous.diff( point ) ); - this.point .set( point ); - this.inside = inside; + // Rectangle + /** @returns {Context2D} */ + fillRect : function (rectangle) { + return office.rect.call(this, 'fillRect', arguments); + }, + /** @returns {Context2D} */ + strokeRect : function (rectangle) { + return office.rect.call(this, 'strokeRect', arguments); + }, + /** @returns {Context2D} */ + clearRect : function (rectangle) { + return office.rect.call(this, 'clearRect', arguments); }, - /** @private */ - eventActions: { - wheel: function (e) { - this.constructor.addWheelDelta(e); - }, - move: function (e) { - this.set(e, true); - }, + // === helpers.text === // - down: function (e) { - this.set(e, true); - }, + /** @returns {Context2D} */ + fillText : function (text, x, y, maxWidth) { + return this.helpers.text.fillText(text, x, y, maxWidth); + }, + /** @returns {Context2D} */ + strokeText : function (text, x, y, maxWidth) { + return this.helpers.text.strokeText(text, x, y, maxWidth); + }, + /** @returns {object} */ + measureText : function (textToMeasure) { + return this.helpers.text.measureText(arguments); + }, + /** @returns {Context2D} */ + text : function (cfg) { + return this.helpers.text.text(cfg); + }, - over: function (e) { - if (this.checkEvent(e)) { - this.fire('enter', e); - } - }, + // === helpers.drawImage === // - out: function (e) { - if (this.checkEvent(e)) { - this.set(e, false); - this.fire('leave', e); - } - } + /** @returns {Context2D} */ + drawImage : function () { + return this.helpers.image.drawImage(arguments); }, - /** @private */ - checkEvent: function (e) { - var related = e.relatedTarget, elem = this.elem; - return related == null || ( - related && related != elem.first && !elem.contains(related) - ); - }, - /** @private */ - listen : function (callback) { - this.elem - .bind({ selectstart: false }) - .bind(atom.object.map( - this.mapping, atom.fn.lambda(callback) - )); - } -}).own({ - prevent: function (e) {e.preventDefault()}, - addWheelDelta: function (e) { - e.delta = - // IE, Opera, Chrome - e.wheelDelta ? e.wheelDelta > 0 ? 1 : -1 : - // Fx - e.detail ? e.detail < 0 ? 1 : -1 : null; + // === helpers.pixels === // - return e; + /** @returns {CanvasPixelArray} */ + createImageData : function (w, h) { + return this.helpers.pixels.createImageData(w, h); }, - eventSource: function (e) { - return e.changedTouches ? e.changedTouches[0] : e; + /** @returns {Context2D} */ + putImageData : function () { + return this.helpers.pixels.putImageData(arguments); + }, + /** @returns {CanvasPixelArray} */ + getImageData : function (rectangle) { + return this.helpers.pixels.getImageData(arguments); + }, + getPixels : function (rectangle) { + return this.helpers.pixels.getPixels(arguments); + }, + getPixel: function (point) { + return this.helpers.pixels.getPixel(point); }, - expandEvent: function (e) { - var source = this.eventSource(e); - if (e.pageX == null) { - e.pageX = source.pageX != null ? source.pageX : source.clientX + document.scrollLeft; - e.pageY = source.pageY != null ? source.pageY : source.clientY + document.scrollTop ; - } + // === helpers.gradients === // - return e; + /** @returns {CanvasGradient} */ + createGradient: function (from, to, colors) { + return this.helpers.gradients.createGradient(from, to, colors); + }, + /** @returns {CanvasGradient} */ + createRectangleGradient: function (rectangle, colors) { + return this.helpers.gradients.createRectangleGradient(rectangle, colors); + }, + /** @returns {CanvasGradient} */ + createLinearGradient : function () { + return this.helpers.gradients.createLinearGradient(arguments); + }, + /** @returns {CanvasGradient} */ + createRadialGradient: function () { + return this.helpers.gradients.createRadialGradient(arguments); }, - getOffset : function (e, element) { - var elementOffset = atom.dom(element || this.eventSource(e).target).offset(); - this.expandEvent(e); + // === etc === // - return new Point( - e.pageX - elementOffset.x, - e.pageY - elementOffset.y - ); + /** @returns {CanvasPattern} */ + createPattern : function () { + return this.original('createPattern', arguments, true); + }, + + drawWindow : function () { + return this.original('drawWindow', arguments); } + +}).own(constants); + + +[ 'fillStyle','font','globalAlpha','globalCompositeOperation','lineCap', + 'lineJoin','lineWidth','miterLimit','shadowOffsetX','shadowColor', + 'strokeStyle','textAlign','textBaseline' + // we'll set this values manually because of bug in Mobile Phones + // 'shadowOffsetY','shadowBlur' +].forEach(function (property) { + atom.accessors.define(Context2D.prototype, property, { + set: function (value) { + this.safeSet(property, value); + }, + get: function () { + return this.ctx2d[property]; + } + }) }); +Context2D.office = office; + +if (atom.core.isFunction(HTMLCanvasElement.addContext)) { + HTMLCanvasElement.addContext('2d-libcanvas', Context2D); +} + +return Context2D; + +}(); + + /* --- @@ -4482,7 +4489,7 @@ UtilsImage.own({ requires: - LibCanvas - Point - - Rectangle + - Shapes.Rectangle ... */