diff --git a/CHANGELOG.md b/CHANGELOG.md index eceb5456..bf85d57d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,42 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Not released][Not released] +## [1.6.3][2017-05-24] + +### Fixed +- Fixed bug with pointer detection. css 3d transformed stage will not work now. + +## [1.6.2][2017-05-08] + +### Fixed +- Fixed bug with automatic shadow for negative scale values + +## [1.6.1][2017-04-25] + +### Fixed +- Fix pointer position detection + + +### Changed +- moved `globalCompositeOperation` property to `Konva.Node` + +## [1.6.0][2017-04-21] + +### Added +- support of globalCompositeOperation for `Konva.Shape` + +### Fixed +- getAllIntersections now works ok for Text shapes (https://github.com/konvajs/konva/issues/224) + +### Changed +- Konva a bit changed a way to detect pointer position. Now it should be OK to apply css transform on Konva container. https://github.com/konvajs/konva/pull/215 + + +## [1.5.0][2017-03-20] + +### Added +- support for `lineDashOffset` property for `Konva.Shape`. + ## [1.4.0][2017-02-07] ## Added diff --git a/README.md b/README.md index 603e200f..81c6490f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This repository began as a GitHub fork of [ericdrowell/KineticJS](https://github # Quick Look ```html - +
+ ``` -You can use CDN: [https://cdn.rawgit.com/konvajs/konva/1.4.0/konva.min.js](https://cdn.rawgit.com/konvajs/konva/1.4.0/konva.min.js) +You can use CDN: [https://cdn.rawgit.com/konvajs/konva/1.6.3/konva.min.js](https://cdn.rawgit.com/konvajs/konva/1.6.3/konva.min.js) -###2 Load via AMD (requirejs): +### 2 Load via AMD (requirejs): ```javascript define(['./konva'], function(Konva) { @@ -78,7 +78,7 @@ define(['./konva'], function(Konva) { }); ``` -###3 CommonJS style with npm: +### 3 CommonJS style with npm: ```bash npm install konva --save @@ -96,7 +96,7 @@ import Konva from 'konva'; import * as Konva from 'konva'; ``` -###4 Minimal bundle +### 4 Minimal bundle If you are using webpack or browserfy you can use this approach to load only required Konva's parts: @@ -111,7 +111,7 @@ import 'konva/src/shapes/rect'; //now Konva.Rect is available to use ``` -###5 NodeJS +### 5 NodeJS You have to install some deps manually to use Konva in nodejs env. @@ -126,23 +126,23 @@ See file `resources/nodejs-demo.js` for example. Last tested with node@5.10.1, canvas@1.3.14, jsdom@8.5.0 -#Change log +# Change log See [CHANGELOG.md](https://github.com/konvajs/konva/blob/master/CHANGELOG.md). -#Dev environment +# Dev environment Before doing all dev stuff make sure you have node installed. After that, run `npm install` in the main directory to install the node module dependencies. Run `gulp -T` to see all build options. -##Building the Konva Framework +## Building the Konva Framework To build a development version of the framework, run `gulp dev-build`. To run a full build, which also produces the minified version run `gulp build`. If you add a file in the src directory, be sure to add the filename to the sourceFiles array variable in `gulpfile.js`. -##Testing +## Testing Konva uses Mocha for testing. @@ -152,11 +152,11 @@ Konva uses Mocha for testing. Konva is covered with hundreds of tests and well over a thousand assertions. Konva uses TDD (test driven development) which means that every new feature or bug fix is accompanied with at least one new test. -##Generate documentation +## Generate documentation Run `gulp api` which will build the documentation files and place them in the `api` folder. -#Pull Requests +# Pull Requests I'd be happy to review any pull requests that may better the Konva project, in particular if you have a bug fix, enhancement, or a new shape (see `src/shapes` for examples). Before doing so, please first make sure that all of the tests pass (`gulp lint test`). diff --git a/konva.d.ts b/konva.d.ts index 61bdb68a..07b8542a 100644 --- a/konva.d.ts +++ b/konva.d.ts @@ -1,297 +1,297 @@ declare module Konva { - var pixelRatio : number; + var pixelRatio: number; var dragDistance: number; - var isDragging : () => boolean; - var isDragReady : () => boolean; - var DD : any; + var isDragging: () => boolean; + var isDragReady: () => boolean; + var DD: any; export class Util { - static getRandomColor() : string; - static getRGB(color: string) : string; + static getRandomColor(): string; + static getRGB(color: string): string; } export class Easings { - static BackEaseIn() : any; - static BackEaseInOut() : any; - static BackEaseOut() : any; - static BounceEaseIn() : any; - static BounceEaseInOut() : any; - static BounceEaseOut() : any; - static EaseIn() : any; - static EaseInOut() : any; - static EaseOut() : any; - static ElasticEaseIn() : any; - static ElasticEaseInOut() : any; - static ElasticEaseOut() : any; - static Linear() : any; - static StrongEaseIn() : any; - static StrongEaseInOut() : any; - static StrongEaseOut() : any; + static BackEaseIn(): any; + static BackEaseInOut(): any; + static BackEaseOut(): any; + static BounceEaseIn(): any; + static BounceEaseInOut(): any; + static BounceEaseOut(): any; + static EaseIn(): any; + static EaseInOut(): any; + static EaseOut(): any; + static ElasticEaseIn(): any; + static ElasticEaseInOut(): any; + static ElasticEaseOut(): any; + static Linear(): any; + static StrongEaseIn(): any; + static StrongEaseInOut(): any; + static StrongEaseOut(): any; } class Filter { } export class Filters { - static Blur(imageData : any): Filter; - static Brighten(imageData : any): Filter; - static Emboss(imageData : any): Filter; - static Enhance(imageData : any): Filter; - static Grayscale(imageData : any): Filter; - static HSV(imageData : any): Filter; - static Invert(imageData : any): Filter; - static Mask(imageData : any): Filter; - static Noise(imageData : any): Filter; - static Pixelate(imageData : any): Filter; - static Posterize(imageData : any): Filter; - static RGB(imageData : any): Filter; - static RGA(imageData : any): Filter; - static Sepia(imageData : any): Filter; - static Solarize(imageData : any): Filter; - static Threshold(imageData : any): Filter; + static Blur(imageData: any): Filter; + static Brighten(imageData: any): Filter; + static Emboss(imageData: any): Filter; + static Enhance(imageData: any): Filter; + static Grayscale(imageData: any): Filter; + static HSV(imageData: any): Filter; + static Invert(imageData: any): Filter; + static Mask(imageData: any): Filter; + static Noise(imageData: any): Filter; + static Pixelate(imageData: any): Filter; + static Posterize(imageData: any): Filter; + static RGB(imageData: any): Filter; + static RGA(imageData: any): Filter; + static Sepia(imageData: any): Filter; + static Solarize(imageData: any): Filter; + static Threshold(imageData: any): Filter; } export class Animation { constructor(func: Function, layers?: Layer[]); constructor(func: Function, layer?: Layer); - addLayer(layer: Layer) : boolean; - getLayers() : Layer[]; - isRunning() : boolean; - setLayers(layers : Layer[]) : Animation; - setLayers(layer : Layer) : Animation; - start() : Animation; - stop() : Animation; + addLayer(layer: Layer): boolean; + getLayers(): Layer[]; + isRunning(): boolean; + setLayers(layers: Layer[]): Animation; + setLayers(layer: Layer): Animation; + start(): Animation; + stop(): Animation; } interface NodeConfig { x?: number; y?: number; - width? : number; - height? : number; + width?: number; + height?: number; visible?: boolean; listening?: boolean; id?: string; name?: string; opacity?: Number; scale?: Vector2d; - scaleX? : number; - scaleY? : number; + scaleX?: number; + scaleY?: number; rotation?: number; rotationDeg?: number; offset?: Vector2d; - offsetX? : number; - offsetY? : number; + offsetX?: number; + offsetY?: number; draggable?: boolean; dragBoundFunc?: Function; } interface SizeConfig { - x? : number; - y? : number; - width? : number; - height? : number; + x?: number; + y?: number; + width?: number; + height?: number; } interface ToDataURLConfig extends SizeConfig { - callback : Function; - mimeType? : string; - quality? : number; + callback: Function; + mimeType?: string; + quality?: number; } - interface CacheConfig extends SizeConfig{ - drawBorder? : boolean; + interface CacheConfig extends SizeConfig { + drawBorder?: boolean; } - interface ClearConfig extends SizeConfig{ + interface ClearConfig extends SizeConfig { } class Node { - constructor (config: NodeConfig); - static create(data: any, container?: HTMLElement) : T; + constructor(config: NodeConfig); + static create(data: any, container?: HTMLElement): T; - blue() : number; - blue(blue: number) : Node; - brightness() : number; - brightness(brightness: number) : Node; - blurRadius() : number; - blurRadius(radius: number) : Node; - cache(config?: CacheConfig) : Node; - clearCache() : Node; - clear(bounds?: ClearConfig) : Node; - clone(attrs? : NodeConfig): Node; - destroy() : void; + blue(): number; + blue(blue: number): Node; + brightness(): number; + brightness(brightness: number): Node; + blurRadius(): number; + blurRadius(radius: number): Node; + cache(config?: CacheConfig): Node; + clearCache(): Node; + clear(bounds?: ClearConfig): Node; + clone(attrs?: NodeConfig): Node; + destroy(): void; - dragBoundFunc() : Function; - dragBoundFunc(dragBoundFunc: Function) : Node; - draggable() : boolean; - draggable(draggable: boolean) : Node; - draw() : Node; - embossBlend() : boolean; - embossBlend(embossBlend: boolean) : Node; - embossDirection() : string; - embossDirection(embossDirection: string) : Node; - embossStrength() : number; - embossStrength(level: number) : Node; - embossWhiteLevel() : number; - embossWhiteLevel(embossWhiteLevel: number) : Node; - enhance() : number; - enhance(enhance: number) : Node; - filters() : Filter[]; - filters(filters : Filter) : Node; - fire(eventType: string, evt?: any, bubble?: boolean) : Node; + dragBoundFunc(): Function; + dragBoundFunc(dragBoundFunc: Function): Node; + draggable(): boolean; + draggable(draggable: boolean): Node; + draw(): Node; + embossBlend(): boolean; + embossBlend(embossBlend: boolean): Node; + embossDirection(): string; + embossDirection(embossDirection: string): Node; + embossStrength(): number; + embossStrength(level: number): Node; + embossWhiteLevel(): number; + embossWhiteLevel(embossWhiteLevel: number): Node; + enhance(): number; + enhance(enhance: number): Node; + filters(): Filter[]; + filters(filters: Filter): Node; + fire(eventType: string, evt?: any, bubble?: boolean): Node; getAbsoluteOpacity(): number; getAbsolutePosition(): Vector2d; getAbsoluteTransform(): Transform; getAbsoluteZIndex(): number; - getAncestors() : Collection; + getAncestors(): Collection; getAttr(attr: string): any; getAttrs(): NodeConfig; // CHECK - getCanvas() : Canvas; - getClassName() : string; + getCanvas(): Canvas; + getClassName(): string; getClientRect(): SizeConfig; - getContext() : Context; - getDepth() : number; - getHeight() : number; - getHitCanvas() : Canvas; - getLayer() : Layer; - getParent() : Container; + getContext(): Context; + getDepth(): number; + getHeight(): number; + getHitCanvas(): Canvas; + getLayer(): Layer; + getParent(): Container; // CHECK - getSize() : { - width : number; - height : number; + getSize(): { + width: number; + height: number; }; - getStage() : Stage; - getTransform() : Transform; - getType() : String; - getWidth() : number; + getStage(): Stage; + getTransform(): Transform; + getType(): String; + getWidth(): number; getZIndex(): number; - green() : number; - green(green: number) : Node; - height() : number; - height(height: number) : Node; + green(): number; + green(green: number): Node; + height(): number; + height(height: number): Node; hide(): void; - hue() : number; - hue(hue: number) : Node; - id() : string; - id(id: string) : Node; + hue(): number; + hue(hue: number): Node; + id(): string; + id(id: string): Node; isDragging(): boolean; isListening(): boolean; isVisible(): boolean; - kaleidoscopeAngle() : number; - kaleidoscopeAngle(kaleidoscopeAngle: number) : Node; - kaleidoscopePower() : number; - kaleidoscopePower(kaleidoscopePower: number) : Node; - levels() : number; - levels(levels: number) : Node; - listening() : any; - listening(listening: boolean) : Node; - listening(listening : string) : Node; - move(move : Vector2d) : Node; - moveDown() : boolean; + kaleidoscopeAngle(): number; + kaleidoscopeAngle(kaleidoscopeAngle: number): Node; + kaleidoscopePower(): number; + kaleidoscopePower(kaleidoscopePower: number): Node; + levels(): number; + levels(levels: number): Node; + listening(): any; + listening(listening: boolean): Node; + listening(listening: string): Node; + move(move: Vector2d): Node; + moveDown(): boolean; moveTo(newContainer: Container): Node; moveToBottom(): boolean; moveToTop(): boolean; moveUp(): boolean; - name() : string; - name(name: string) : Node; - noise() : number; - noise(noise: number) : Node; - off(evtStr : string) : Node; - offset() : Vector2d; - offset(offset: Vector2d) : Node; - offsetX() : number; - offsetX(offsetX: number) : Node; - offsetY() : number; - offsetY(offsetY: number) : Node; - on(evtStr : string, handler: Function) : Node; - opacity() : number; - opacity(opacity: number) : Node; - pixelSize() : number; - pixelSize(pixelSize: number) : Node; - position() : Vector2d; - position(position: Vector2d) : Node; + name(): string; + name(name: string): Node; + noise(): number; + noise(noise: number): Node; + off(evtStr: string): Node; + offset(): Vector2d; + offset(offset: Vector2d): Node; + offsetX(): number; + offsetX(offsetX: number): Node; + offsetY(): number; + offsetY(offsetY: number): Node; + on(evtStr: string, handler: Function): Node; + opacity(): number; + opacity(opacity: number): Node; + pixelSize(): number; + pixelSize(pixelSize: number): Node; + position(): Vector2d; + position(position: Vector2d): Node; preventDefault(): boolean; preventDefault(preventDefault: boolean): Node; - red() : number; - red(red: number) : Node; - remove() : Node; - rotate(theta : number) : Node; - rotation() : number; - rotation(rotation: number) : Node; - saturation() : number; - saturation(saturation: number) : Node; - scale() : Vector2d; - scale(scale: Vector2d) : Node; - scaleX() : number; - scaleX(scaleX: number) : Node; - scaleY() : number; - scaleY(scaleY: number) : Node; - setAbsolutePosition(pos : Vector2d) : Node; - setAttr(attr: string, val : any): Node; - setAttrs(attrs: NodeConfig) : void; - setId(id: string) : Node; - setSize(size: {width:number; height: number}) : Node; + red(): number; + red(red: number): Node; + remove(): Node; + rotate(theta: number): Node; + rotation(): number; + rotation(rotation: number): Node; + saturation(): number; + saturation(saturation: number): Node; + scale(): Vector2d; + scale(scale: Vector2d): Node; + scaleX(): number; + scaleX(scaleX: number): Node; + scaleY(): number; + scaleY(scaleY: number): Node; + setAbsolutePosition(pos: Vector2d): Node; + setAttr(attr: string, val: any): Node; + setAttrs(attrs: NodeConfig): void; + setId(id: string): Node; + setSize(size: { width: number; height: number }): Node; setZIndex(zIndex: number): void; - shouldDrawHit() : boolean; - show() : Node; - skew() : Vector2d; - skew(skew: Vector2d) : Node; - skewX() : number; - skewX(skewX: number) : Node; - skewY() : number; - skewY(skewY: number) : Node; - startDrag() : void; - stopDrag() : void; - threshold() : number; - threshold(threshold: number) : Node; - toDataURL(config: ToDataURLConfig) : string; - toImage(config: ToDataURLConfig) : HTMLImageElement; - toJSON() : string; - toObject() : any; - transformsEnabled() : string; - transformsEnabled(transformsEnabled: string) : Node; - value() : number; - value(value: number) : Node; - visible() : any; - visible(visible: boolean) : Node; - visible(visible: string) : Node; - width() : number; - width(width: number) : Node; - x() : number; - x(x: number) : Node; - y() : number; - y(y: number) : Node; + shouldDrawHit(): boolean; + show(): Node; + skew(): Vector2d; + skew(skew: Vector2d): Node; + skewX(): number; + skewX(skewX: number): Node; + skewY(): number; + skewY(skewY: number): Node; + startDrag(): void; + stopDrag(): void; + threshold(): number; + threshold(threshold: number): Node; + toDataURL(config: ToDataURLConfig): string; + toImage(config: ToDataURLConfig): HTMLImageElement; + toJSON(): string; + toObject(): any; + transformsEnabled(): string; + transformsEnabled(transformsEnabled: string): Node; + value(): number; + value(value: number): Node; + visible(): any; + visible(visible: boolean): Node; + visible(visible: string): Node; + width(): number; + width(width: number): Node; + x(): number; + x(x: number): Node; + y(): number; + y(y: number): Node; } interface ContainerConfig extends NodeConfig { clearBeforeDraw?: boolean; - clipFunc?: Function; + clipFunc?: (ctx: CanvasRenderingContext2D) => void; } class Container extends Node { constructor(params?: ContainerConfig); - add(child : Node): Container; - getChildren(filterfunc?: Function) : Collection; + add(child: Node): Container; + getChildren(filterfunc?: Function): Collection; clip(): SizeConfig; - clip(clip: SizeConfig) : Container; + clip(clip: SizeConfig): Container; clipHeight(): number; - clipHeight(clipHeight: number) : Container; + clipHeight(clipHeight: number): Container; clipWidth(): number; - clipWidth(clipWidth: number) : Container; + clipWidth(clipWidth: number): Container; clipX(): number; - clipX(clipX: number) : Container; + clipX(clipX: number): Container; clipY(): number; - clipY(clipY: number) : Container; - clipFunct(): number; - clipFunct(clipFunc: Function) : Container; - destroyChildren() : void; - find(selector? : string): Collection; + clipY(clipY: number): Container; + clipFunc(): (ctx: CanvasRenderingContext2D) => void; + clipFunc(ctx: CanvasRenderingContext2D): void; + destroyChildren(): void; + find(selector?: string): Collection; getAllIntersections(pos: Vector2d): Node[]; - hasChildren() : boolean; - removeChildren() : void; + hasChildren(): boolean; + removeChildren(): void; } interface ShapeConfig extends NodeConfig { @@ -310,14 +310,14 @@ declare module Konva { fillLinearGradientStartPoint?: Vector2d; fillLinearGradientStartPointX?: number; fillLinearGradientStartPointY?: number; - fillLinearGradientEndPoint? : Vector2d; + fillLinearGradientEndPoint?: Vector2d; fillLinearGradientEndPointX?: number; fillLinearGradientEndPointY?: number; fillLinearGradientColorStops?: Array; fillLinearRadialStartPoint?: Vector2d; fillLinearRadialStartPointX?: number; fillLinearRadialStartPointY?: number; - fillLinearRadialEndPoint? : Vector2d; + fillLinearRadialEndPoint?: Vector2d; fillLinearRadialEndPointX?: number; fillLinearRadialEndPointY?: number; fillRadialGradientStartRadius?: number; @@ -331,14 +331,14 @@ declare module Konva { strokeEnabled?: boolean; lineJoin?: string; lineCap?: string; - sceneFunc? : (con : Context) => void; - hitFunc? : (con : Context) => void; - drawFunc? : (con : Context) => void; + sceneFunc?: (con: Context) => void; + hitFunc?: (con: Context) => void; + drawFunc?: (con: Context) => void; shadowColor?: string; shadowBlur?: number; - shadowOffset? : Vector2d; - shadowOffsetX? : number; - shadowOffsetY? : number; + shadowOffset?: Vector2d; + shadowOffsetX?: number; + shadowOffsetY?: number; shadowOpacity?: number; shadowEnabled?: boolean; dash?: number[]; @@ -346,26 +346,26 @@ declare module Konva { } class Shape extends Node { - constructor(ShapeConfig : ShapeConfig); - dash() : number[]; + constructor(ShapeConfig: ShapeConfig); + dash(): number[]; dash(dash: number[]): Shape; - dashEnabled() : boolean; + dashEnabled(): boolean; dashEnabled(dashEnabled: boolean): Shape; drawHitFromCache(alphaThreshold: number): Shape; - fill() : string; + fill(): string; fill(fill: string): Shape; - fillEnabled() : boolean; + fillEnabled(): boolean; fillEnabled(fillEnabled: boolean): Shape; - fillLinearGradientColorStops() : Array; - fillLinearGradientColorStops(colors: Array): Shape; + fillLinearGradientColorStops(): Array; + fillLinearGradientColorStops(colors: Array): Shape; fillLinearGradientStartPoint(): Vector2d; fillLinearGradientStartPoint(point: Vector2d): Vector2d; fillLinearGradientStartPointX(): number; fillLinearGradientStartPointX(x: number): Shape; fillLinearGradientStartPointY(): number; fillLinearGradientStartPointY(y: number): Shape; - fillLinearGradientEndPoint() : Vector2d; - fillLinearGradientEndPoint(point: Vector2d) : Shape; + fillLinearGradientEndPoint(): Vector2d; + fillLinearGradientEndPoint(point: Vector2d): Shape; fillLinearGradientEndPointX(): number; fillLinearGradientEndPointX(x: number): Shape; fillLinearGradientEndPointY(): number; @@ -376,8 +376,8 @@ declare module Konva { fillLinearRadialStartPointX(x: number): Shape; fillLinearRadialStartPointY(): number; fillLinearRadialStartPointY(y: number): Shape; - fillLinearRadialEndPoint() : Vector2d; - fillLinearRadialEndPoint(point: Vector2d) : Vector2d; + fillLinearRadialEndPoint(): Vector2d; + fillLinearRadialEndPoint(point: Vector2d): Vector2d; fillLinearRadialEndPointX(): number; fillLinearRadialEndPointX(x: number): Shape; fillLinearRadialEndPointY(): number; @@ -388,20 +388,20 @@ declare module Konva { fillRadialGradientStartRadius(radius: number): Shape; fillRadialGradientEndRadius(): number; fillRadialGradientEndRadius(radius: number): Shape; - fillRadialGradientColorStops(): Array; - fillRadialGradientColorStops(color: Array): Shape; + fillRadialGradientColorStops(): Array; + fillRadialGradientColorStops(color: Array): Shape; fillPatternOffset(): Vector2d; - fillPatternOffset(offset: Vector2d) : Shape; + fillPatternOffset(offset: Vector2d): Shape; fillPatternOffsetX(): number; fillPatternOffsetX(x: number): Shape; fillPatternOffsetY(): number; fillPatternOffsetY(y: number): Shape; - fillPatternRepeat() : string; + fillPatternRepeat(): string; fillPatternRepeat(repeat: string): Shape; fillPatternRotation(): number; fillPatternRotation(rotation: number): Shape; fillPatternScale(): Vector2d; - fillPatternScale(scale: Vector2d) : Shape; + fillPatternScale(scale: Vector2d): Shape; fillPatternScaleX(): number; fillPatternScaleX(x: number): Shape; fillPatternScaleY(): number; @@ -418,35 +418,35 @@ declare module Konva { hitFunc(): Function; hitFunc(func: Function): Shape; intersects(point: Vector2d): boolean; - lineCap() : string; + lineCap(): string; lineCap(lineCap: string): Shape; - lineJoin() : string; + lineJoin(): string; lineJoin(lineJoin: string): Shape; sceneFunc(): Function; - sceneFunc(func: (con : Context) => {}): Shape; - shadowColor() : string; + sceneFunc(func: (con: Context) => {}): Shape; + shadowColor(): string; shadowColor(shadowColor: string): Shape; - shadowEnabled() : boolean; + shadowEnabled(): boolean; shadowEnabled(shadowEnabled: boolean): Shape; - shadowOffset() : Vector2d; + shadowOffset(): Vector2d; shadowOffset(shadowOffset: Vector2d): Shape; - shadowOffsetX() : number; + shadowOffsetX(): number; shadowOffsetX(shadowOffsetX: number): Shape; - shadowOffsetY() : number; + shadowOffsetY(): number; shadowOffsetY(shadowOffsetY: number): Shape; - shadowOpacity() : number; + shadowOpacity(): number; shadowOpacity(shadowOpacity: number): Shape; - shadowBlur() : number; + shadowBlur(): number; shadowBlur(blur: number): Shape; - stroke() : string; + stroke(): string; stroke(stroke: string): Shape; - strokeEnabled() : boolean; + strokeEnabled(): boolean; strokeEnabled(strokeEnabled: boolean): Shape; - strokeScaleEnabled() : boolean; + strokeScaleEnabled(): boolean; strokeScaleEnabled(strokeScaleEnabled: boolean): Shape; - strokeHitEnabled() : boolean; + strokeHitEnabled(): boolean; strokeHitEnabled(strokeHitEnabled: boolean): Shape; - strokeWidth() : number; + strokeWidth(): number; strokeWidth(strokeWidth: number): Shape; } @@ -455,35 +455,43 @@ declare module Konva { } class Stage extends Container { - constructor(StageConfig : StageConfig); + constructor(StageConfig: StageConfig); add(layer: Layer): Stage; + add(layer: FastLayer): Stage; batchDraw(): void; container(): HTMLElement; - destroy() : void; + destroy(): void; drawHit(): void; - getIntersection(pos: Vector2d) : Shape; + getIntersection(pos: Vector2d): Shape; getLayers(): Layer[]; getPointerPosition(): Vector2d; setContainer(con: HTMLElement): void; - setHeight(height: number) : void; - setWidth(width: number) : void; + setHeight(height: number): void; + setWidth(width: number): void; } interface LayerConfig extends ContainerConfig { clearBeforeDraw?: boolean; } + class FastLayer extends Container { + constructor (config?: ContainerConfig); + drawScene(): void; + hitGraphEnabled(val: boolean): FastLayer; + batchDraw(): void; + } + class Layer extends Container { - constructor (config?: LayerConfig); + constructor(config?: LayerConfig); getIntersection(pos: Vector2d): Shape; enableHitGraph(): Layer; disableHitGraph(): Layer; - clearBeforeDraw() : boolean; + clearBeforeDraw(): boolean; clearBeforeDraw(val: boolean): Layer; hitGraphEnabled(): boolean; hitGraphEnabled(val: boolean): Layer; batchDraw(): void; - drawScene() : void; + drawScene(): void; } class Group extends Container { @@ -497,32 +505,32 @@ declare module Konva { } class Canvas { - constructor(CanvasConfig : CanvasConfig); + constructor(CanvasConfig: CanvasConfig); getContext(): CanvasRenderingContext2D; getHeight(): number; getWidth(): number; getPixelRatio(): number; - setHeight(val: number) : void; - setWidth(val: number) : void ; - setPixelRatio(val: number) : void; - setSize(size: {width:number; height: number}) : void; - toDataURL(mimeType: string, quality: number) : string; - public _canvas : HTMLElement; + setHeight(val: number): void; + setWidth(val: number): void; + setPixelRatio(val: number): void; + setSize(size: { width: number; height: number }): void; + toDataURL(mimeType: string, quality: number): string; + public _canvas: HTMLElement; } class Context { - clear(bounds?: ClearConfig) : Context; + clear(bounds?: ClearConfig): Context; clearTrace(): void; fillShape(shape: Shape): void; fillStrokeShape(shape: Shape): void; - getCanvas() : Canvas; + getCanvas(): Canvas; getTrace(relaxed: boolean): string; reset(): void; - moveTo(x : number, y : number) : void; - lineTo(x : number, y : number) : void; - beginPath() : void; - setAttr(attr : string, value : any) : void; - closePath() : void; + moveTo(x: number, y: number): void; + lineTo(x: number, y: number): void; + beginPath(): void; + setAttr(attr: string, value: any): void; + closePath(): void; strokeShape(shape: Shape): void; } @@ -546,23 +554,30 @@ declare module Konva { } class Ring extends Shape { - constructor(RingConfig : RingConfig); - angle(): number; - angle(angle: number): Ring; + constructor(RingConfig: RingConfig); innerRadius(): number; innerRadius(innerRadius: number): Ring; outerRadius(): number; outerRadius(outerRadius: number): Ring; } - interface ArcConfig extends RingConfig { + interface ArcConfig extends ShapeConfig { angle: number; + innerRadius: number; + outerRadius: number; + clockwise?: boolean; } class Arc extends Shape { - constructor(ArcConfig : ArcConfig); + constructor(ArcConfig: ArcConfig); + angle(): number; + angle(angle: number): Ring; clockwise(): boolean; clockwise(clockwise: boolean): Arc; + innerRadius(): number; + innerRadius(innerRadius: number): Arc; + outerRadius(): number; + outerRadius(outerRadius: number): Arc; } interface CircleConfig extends ShapeConfig { @@ -570,7 +585,7 @@ declare module Konva { } class Circle extends Shape { - constructor(CircleConfig : CircleConfig); + constructor(CircleConfig: CircleConfig); radius(): number; radius(radius: number): Circle; } @@ -580,7 +595,7 @@ declare module Konva { } class Ellipse extends Shape { - constructor(EllipseConfig : EllipseConfig); + constructor(EllipseConfig: EllipseConfig); radius(): any; radius(radius: any): Ellipse; radiusX(): number; @@ -595,7 +610,7 @@ declare module Konva { } class Image extends Shape { - constructor(ImageConfig : ImageConfig); + constructor(ImageConfig: ImageConfig); image(): HTMLImageElement; image(image: HTMLImageElement): Image; crop(): SizeConfig; @@ -617,7 +632,7 @@ declare module Konva { } class Line extends Shape { - constructor(LineConfig : LineConfig); + constructor(LineConfig: LineConfig); closed(): boolean; closed(closed: boolean): Line; tension(): number; @@ -635,7 +650,7 @@ declare module Konva { } class Arrow extends Shape { - constructor(ArrowConfig : ArrowConfig); + constructor(ArrowConfig: ArrowConfig); closed(): boolean; closed(closed: boolean): Arrow; tension(): number; @@ -653,7 +668,7 @@ declare module Konva { } class Rect extends Shape { - constructor(RectConfig : RectConfig); + constructor(RectConfig: RectConfig); cornerRadius(): number; cornerRadius(cornerRadius: number): Rect; } @@ -666,7 +681,7 @@ declare module Konva { } class Sprite extends Shape { - constructor(SpriteConfig : SpriteConfig); + constructor(SpriteConfig: SpriteConfig); start(): void; stop(): void; animation(): string; @@ -693,7 +708,7 @@ declare module Konva { } class Text extends Shape { - constructor(TextConfig : TextConfig); + constructor(TextConfig: TextConfig); getTextWidth(): number; getTextHeight(): number; text(): string; @@ -723,7 +738,7 @@ declare module Konva { } class Wedge extends Shape { - constructor(WedgeConfig : WedgeConfig); + constructor(WedgeConfig: WedgeConfig); angle(): number; angle(angle: number): Wedge; radius(): number; @@ -737,11 +752,11 @@ declare module Konva { pointerDirection?: string; pointerWidth?: number; pointerHeight?: number; - cornerRadius?:number; + cornerRadius?: number; } class Tag extends Shape { - constructor(config : TagConfig); + constructor(config: TagConfig); pointerDirection(): string; pointerDirection(pointerDirection: string): Tag; pointerWidth(): number; @@ -757,7 +772,7 @@ declare module Konva { } class Label extends Group { - constructor(LabelInterface : LabelInterface); + constructor(LabelInterface: LabelInterface); getText(): Text; getTag(): Rect; } @@ -767,7 +782,7 @@ declare module Konva { } class Path extends Shape { - constructor(PathConfig : PathConfig); + constructor(PathConfig: PathConfig); data(): string; data(data: string): Path; } @@ -778,7 +793,7 @@ declare module Konva { } class RegularPolygon extends Shape { - constructor(RegularPolygonConfig : RegularPolygonConfig); + constructor(RegularPolygonConfig: RegularPolygonConfig); sides(): number; sides(sides: number): RegularPolygon; radius(): number; @@ -792,7 +807,7 @@ declare module Konva { } class Star extends Shape { - constructor(StarConfig : StarConfig); + constructor(StarConfig: StarConfig); numPoints(): number; numPoints(numPoints: number): Star; innerRadius(): number; @@ -810,7 +825,7 @@ declare module Konva { } class TextPath extends Shape { - constructor(TextPathConfig : TextPathConfig); + constructor(TextPathConfig: TextPathConfig); getTextWidth(): number; getTextHeight(): number; setText(text: string): void; @@ -826,25 +841,25 @@ declare module Konva { class Collection { - [i : number] : any; + [i: number]: any; static toCollection(arr: any[]): Collection; - each(f: (el : Node) => void): void; - toArray() : any[]; + each(f: (el: Node) => void): void; + toArray(): any[]; length: number; } class Transform { copy(): Transform; getMatrix(): any[]; - getTranslation() : Vector2d; - invert() : void; + getTranslation(): Vector2d; + invert(): void; multiply(matrix: any[]): void; point(point: Vector2d): Vector2d; - rotate(deg: number) : void; - scale(x: number, y: Number) : void; - setAbsolutePosition() : void; - skew(x: number, y: Number) : void; - translate(x: number, y: Number) : void; + rotate(deg: number): void; + scale(x: number, y: Number): void; + setAbsolutePosition(): void; + skew(x: number, y: Number): void; + translate(x: number, y: Number): void; } diff --git a/konva.js b/konva.js index 18c2200f..aea08c47 100644 --- a/konva.js +++ b/konva.js @@ -1,9 +1,9 @@ /* - * Konva JavaScript Framework v1.4.0 + * Konva JavaScript Framework v1.6.3 * http://konvajs.github.io/ * Licensed under the MIT or GPL Version 2 licenses. - * Date: Sun Apr 09 2017 + * Date: Wed Jul 05 2017 * * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) * Modified work Copyright (C) 2014 - 2015 by Anton Lavrenov (Konva) @@ -39,7 +39,7 @@ var Konva = { // public - version: '1.4.0', + version: '1.6.3', // private stages: [], @@ -1497,112 +1497,127 @@ })(); (function() { - 'use strict'; - var COMMA = ',', - OPEN_PAREN = '(', - CLOSE_PAREN = ')', - OPEN_PAREN_BRACKET = '([', - CLOSE_BRACKET_PAREN = '])', - SEMICOLON = ';', - DOUBLE_PAREN = '()', - // EMPTY_STRING = '', - EQUALS = '=', - // SET = 'set', - CONTEXT_METHODS = [ - 'arc', - 'arcTo', - 'beginPath', - 'bezierCurveTo', - 'clearRect', - 'clip', - 'closePath', - 'createLinearGradient', - 'createPattern', - 'createRadialGradient', - 'drawImage', - 'fill', - 'fillText', - 'getImageData', - 'createImageData', - 'lineTo', - 'moveTo', - 'putImageData', - 'quadraticCurveTo', - 'rect', - 'restore', - 'rotate', - 'save', - 'scale', - 'setLineDash', - 'setTransform', - 'stroke', - 'strokeText', - 'transform', - 'translate' - ]; + 'use strict'; + var COMMA = ',', + OPEN_PAREN = '(', + CLOSE_PAREN = ')', + OPEN_PAREN_BRACKET = '([', + CLOSE_BRACKET_PAREN = '])', + SEMICOLON = ';', + DOUBLE_PAREN = '()', + // EMPTY_STRING = '', + EQUALS = '=', + // SET = 'set', + CONTEXT_METHODS = [ + 'arc', + 'arcTo', + 'beginPath', + 'bezierCurveTo', + 'clearRect', + 'clip', + 'closePath', + 'createLinearGradient', + 'createPattern', + 'createRadialGradient', + 'drawImage', + 'fill', + 'fillText', + 'getImageData', + 'createImageData', + 'lineTo', + 'moveTo', + 'putImageData', + 'quadraticCurveTo', + 'rect', + 'restore', + 'rotate', + 'save', + 'scale', + 'setLineDash', + 'setTransform', + 'stroke', + 'strokeText', + 'transform', + 'translate' + ]; - var CONTEXT_PROPERTIES = ['fillStyle', 'strokeStyle', 'shadowColor', 'shadowBlur', 'shadowOffsetX', - 'shadowOffsetY', 'lineCap', 'lineJoin', 'lineWidth', 'miterLimit', 'font', 'textAlign', 'textBaseline', - 'globalAlpha', 'globalCompositeOperation']; + var CONTEXT_PROPERTIES = [ + 'fillStyle', + 'strokeStyle', + 'shadowColor', + 'shadowBlur', + 'shadowOffsetX', + 'shadowOffsetY', + 'lineCap', + 'lineDashOffset', + 'lineJoin', + 'lineWidth', + 'miterLimit', + 'font', + 'textAlign', + 'textBaseline', + 'globalAlpha', + 'globalCompositeOperation' + ]; - /** + /** * Canvas Context constructor * @constructor * @abstract * @memberof Konva */ - Konva.Context = function(canvas) { - this.init(canvas); - }; + Konva.Context = function(canvas) { + this.init(canvas); + }; - Konva.Context.prototype = { - init: function(canvas) { - this.canvas = canvas; - this._context = canvas._canvas.getContext('2d'); + Konva.Context.prototype = { + init: function(canvas) { + this.canvas = canvas; + this._context = canvas._canvas.getContext('2d'); - if (Konva.enableTrace) { - this.traceArr = []; - this._enableTrace(); - } - }, - /** + if (Konva.enableTrace) { + this.traceArr = []; + this._enableTrace(); + } + }, + /** * fill shape * @method * @memberof Konva.Context.prototype * @param {Konva.Shape} shape */ - fillShape: function(shape) { - if(shape.getFillEnabled()) { - this._fill(shape); - } - }, - /** + fillShape: function(shape) { + if (shape.getFillEnabled()) { + this._fill(shape); + } + }, + /** * stroke shape * @method * @memberof Konva.Context.prototype * @param {Konva.Shape} shape */ - strokeShape: function(shape) { - if(shape.getStrokeEnabled()) { - this._stroke(shape); - } - }, - /** + strokeShape: function(shape) { + if (shape.getStrokeEnabled()) { + this._stroke(shape); + } + }, + /** * fill then stroke * @method * @memberof Konva.Context.prototype * @param {Konva.Shape} shape */ - fillStrokeShape: function(shape) { - var fillEnabled = shape.getFillEnabled(); - if(fillEnabled) { - this._fill(shape); - } - if(shape.getStrokeEnabled()) { - this._stroke(shape); - } - }, - /** + fillStrokeShape: function(shape) { + var fillEnabled = shape.getFillEnabled(); + if (fillEnabled) { + this._fill(shape); + } + if (shape.getStrokeEnabled()) { + this._stroke(shape); + } + }, + /** * get context trace if trace is enabled * @method * @memberof Konva.Context.prototype @@ -1611,83 +1626,83 @@ * properites. * @returns {String} */ - getTrace: function(relaxed) { - var traceArr = this.traceArr, - len = traceArr.length, - str = '', - n, trace, method, args; + getTrace: function(relaxed) { + var traceArr = this.traceArr, + len = traceArr.length, + str = '', + n, + trace, + method, + args; - for (n = 0; n < len; n++) { - trace = traceArr[n]; - method = trace.method; + for (n = 0; n < len; n++) { + trace = traceArr[n]; + method = trace.method; - // methods - if (method) { - args = trace.args; - str += method; - if (relaxed) { - str += DOUBLE_PAREN; - } - else { - if (Konva.Util._isArray(args[0])) { - str += OPEN_PAREN_BRACKET + args.join(COMMA) + CLOSE_BRACKET_PAREN; - } - else { - str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN; - } - } - } - // properties - else { - str += trace.property; - if (!relaxed) { - str += EQUALS + trace.val; - } - } - - str += SEMICOLON; + // methods + if (method) { + args = trace.args; + str += method; + if (relaxed) { + str += DOUBLE_PAREN; + } else { + if (Konva.Util._isArray(args[0])) { + str += + OPEN_PAREN_BRACKET + args.join(COMMA) + CLOSE_BRACKET_PAREN; + } else { + str += OPEN_PAREN + args.join(COMMA) + CLOSE_PAREN; } + } + } else { + // properties + str += trace.property; + if (!relaxed) { + str += EQUALS + trace.val; + } + } - return str; - }, - /** + str += SEMICOLON; + } + + return str; + }, + /** * clear trace if trace is enabled * @method * @memberof Konva.Context.prototype */ - clearTrace: function() { - this.traceArr = []; - }, - _trace: function(str) { - var traceArr = this.traceArr, - len; + clearTrace: function() { + this.traceArr = []; + }, + _trace: function(str) { + var traceArr = this.traceArr, len; - traceArr.push(str); - len = traceArr.length; + traceArr.push(str); + len = traceArr.length; - if (len >= Konva.traceArrMax) { - traceArr.shift(); - } - }, - /** + if (len >= Konva.traceArrMax) { + traceArr.shift(); + } + }, + /** * reset canvas context transform * @method * @memberof Konva.Context.prototype */ - reset: function() { - var pixelRatio = this.getCanvas().getPixelRatio(); - this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); - }, - /** + reset: function() { + var pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); + }, + /** * get canvas * @method * @memberof Konva.Context.prototype * @returns {Konva.Canvas} */ - getCanvas: function() { - return this.canvas; - }, - /** + getCanvas: function() { + return this.canvas; + }, + /** * clear canvas * @method * @memberof Konva.Context.prototype @@ -1697,428 +1712,469 @@ * @param {Number} [bounds.width] * @param {Number} [bounds.height] */ - clear: function(bounds) { - var canvas = this.getCanvas(); + clear: function(bounds) { + var canvas = this.getCanvas(); - if (bounds) { - this.clearRect(bounds.x || 0, bounds.y || 0, bounds.width || 0, bounds.height || 0); - } - else { - this.clearRect(0, 0, canvas.getWidth() / canvas.pixelRatio, canvas.getHeight() / canvas.pixelRatio); - } - }, - _applyLineCap: function(shape) { - var lineCap = shape.getLineCap(); - if(lineCap) { - this.setAttr('lineCap', lineCap); - } - }, - _applyOpacity: function(shape) { - var absOpacity = shape.getAbsoluteOpacity(); - if (absOpacity !== 1) { - this.setAttr('globalAlpha', absOpacity); - } - }, - _applyLineJoin: function(shape) { - var lineJoin = shape.getLineJoin(); - if(lineJoin) { - this.setAttr('lineJoin', lineJoin); - } - }, - setAttr: function(attr, val) { - this._context[attr] = val; - }, + if (bounds) { + this.clearRect( + bounds.x || 0, + bounds.y || 0, + bounds.width || 0, + bounds.height || 0 + ); + } else { + this.clearRect( + 0, + 0, + canvas.getWidth() / canvas.pixelRatio, + canvas.getHeight() / canvas.pixelRatio + ); + } + }, + _applyLineCap: function(shape) { + var lineCap = shape.getLineCap(); + if (lineCap) { + this.setAttr('lineCap', lineCap); + } + }, + _applyOpacity: function(shape) { + var absOpacity = shape.getAbsoluteOpacity(); + if (absOpacity !== 1) { + this.setAttr('globalAlpha', absOpacity); + } + }, + _applyLineJoin: function(shape) { + var lineJoin = shape.getLineJoin(); + if (lineJoin) { + this.setAttr('lineJoin', lineJoin); + } + }, + setAttr: function(attr, val) { + this._context[attr] = val; + }, - // context pass through methods - arc: function() { - var a = arguments; - this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - beginPath: function() { - this._context.beginPath(); - }, - bezierCurveTo: function() { - var a = arguments; - this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - clearRect: function() { - var a = arguments; - this._context.clearRect(a[0], a[1], a[2], a[3]); - }, - clip: function() { - this._context.clip(); - }, - closePath: function() { - this._context.closePath(); - }, - createImageData: function() { - var a = arguments; - if(a.length === 2) { - return this._context.createImageData(a[0], a[1]); - } - else if(a.length === 1) { - return this._context.createImageData(a[0]); - } - }, - createLinearGradient: function() { - var a = arguments; - return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); - }, - createPattern: function() { - var a = arguments; - return this._context.createPattern(a[0], a[1]); - }, - createRadialGradient: function() { - var a = arguments; - return this._context.createRadialGradient(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - drawImage: function() { - var a = arguments, - _context = this._context; + // context pass through methods + arc: function() { + var a = arguments; + this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + beginPath: function() { + this._context.beginPath(); + }, + bezierCurveTo: function() { + var a = arguments; + this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + clearRect: function() { + var a = arguments; + this._context.clearRect(a[0], a[1], a[2], a[3]); + }, + clip: function() { + this._context.clip(); + }, + closePath: function() { + this._context.closePath(); + }, + createImageData: function() { + var a = arguments; + if (a.length === 2) { + return this._context.createImageData(a[0], a[1]); + } else if (a.length === 1) { + return this._context.createImageData(a[0]); + } + }, + createLinearGradient: function() { + var a = arguments; + return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); + }, + createPattern: function() { + var a = arguments; + return this._context.createPattern(a[0], a[1]); + }, + createRadialGradient: function() { + var a = arguments; + return this._context.createRadialGradient( + a[0], + a[1], + a[2], + a[3], + a[4], + a[5] + ); + }, + drawImage: function() { + var a = arguments, _context = this._context; - if(a.length === 3) { - _context.drawImage(a[0], a[1], a[2]); - } - else if(a.length === 5) { - _context.drawImage(a[0], a[1], a[2], a[3], a[4]); - } - else if(a.length === 9) { - _context.drawImage(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); - } - }, - isPointInPath: function(x, y) { - return this._context.isPointInPath(x, y); - }, - fill: function() { - this._context.fill(); - }, - fillRect: function(x, y, width, height) { - this._context.fillRect(x, y, width, height); - }, - strokeRect: function(x, y, width, height) { - this._context.strokeRect(x, y, width, height); - }, - fillText: function() { - var a = arguments; - this._context.fillText(a[0], a[1], a[2]); - }, - measureText: function(text) { - return this._context.measureText(text); - }, - getImageData: function() { - var a = arguments; - return this._context.getImageData(a[0], a[1], a[2], a[3]); - }, - lineTo: function() { - var a = arguments; - this._context.lineTo(a[0], a[1]); - }, - moveTo: function() { - var a = arguments; - this._context.moveTo(a[0], a[1]); - }, - rect: function() { - var a = arguments; - this._context.rect(a[0], a[1], a[2], a[3]); - }, - putImageData: function() { - var a = arguments; - this._context.putImageData(a[0], a[1], a[2]); - }, - quadraticCurveTo: function() { - var a = arguments; - this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); - }, - restore: function() { - this._context.restore(); - }, - rotate: function() { - var a = arguments; - this._context.rotate(a[0]); - }, - save: function() { - this._context.save(); - }, - scale: function() { - var a = arguments; - this._context.scale(a[0], a[1]); - }, - setLineDash: function() { - var a = arguments, - _context = this._context; + if (a.length === 3) { + _context.drawImage(a[0], a[1], a[2]); + } else if (a.length === 5) { + _context.drawImage(a[0], a[1], a[2], a[3], a[4]); + } else if (a.length === 9) { + _context.drawImage( + a[0], + a[1], + a[2], + a[3], + a[4], + a[5], + a[6], + a[7], + a[8] + ); + } + }, + isPointInPath: function(x, y) { + return this._context.isPointInPath(x, y); + }, + fill: function() { + this._context.fill(); + }, + fillRect: function(x, y, width, height) { + this._context.fillRect(x, y, width, height); + }, + strokeRect: function(x, y, width, height) { + this._context.strokeRect(x, y, width, height); + }, + fillText: function() { + var a = arguments; + this._context.fillText(a[0], a[1], a[2]); + }, + measureText: function(text) { + return this._context.measureText(text); + }, + getImageData: function() { + var a = arguments; + return this._context.getImageData(a[0], a[1], a[2], a[3]); + }, + lineTo: function() { + var a = arguments; + this._context.lineTo(a[0], a[1]); + }, + moveTo: function() { + var a = arguments; + this._context.moveTo(a[0], a[1]); + }, + rect: function() { + var a = arguments; + this._context.rect(a[0], a[1], a[2], a[3]); + }, + putImageData: function() { + var a = arguments; + this._context.putImageData(a[0], a[1], a[2]); + }, + quadraticCurveTo: function() { + var a = arguments; + this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); + }, + restore: function() { + this._context.restore(); + }, + rotate: function() { + var a = arguments; + this._context.rotate(a[0]); + }, + save: function() { + this._context.save(); + }, + scale: function() { + var a = arguments; + this._context.scale(a[0], a[1]); + }, + setLineDash: function() { + var a = arguments, _context = this._context; - // works for Chrome and IE11 - if(this._context.setLineDash) { - _context.setLineDash(a[0]); - } - // verified that this works in firefox - else if('mozDash' in _context) { - _context.mozDash = a[0]; - } - // does not currently work for Safari - else if('webkitLineDash' in _context) { - _context.webkitLineDash = a[0]; - } + // works for Chrome and IE11 + if (this._context.setLineDash) { + _context.setLineDash(a[0]); + } else if ('mozDash' in _context) { + // verified that this works in firefox + _context.mozDash = a[0]; + } else if ('webkitLineDash' in _context) { + // does not currently work for Safari + _context.webkitLineDash = a[0]; + } - // no support for IE9 and IE10 - }, - getLineDash: function() { - return this._context.getLineDash(); - }, - setTransform: function() { - var a = arguments; - this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - stroke: function() { - this._context.stroke(); - }, - strokeText: function() { - var a = arguments; - this._context.strokeText(a[0], a[1], a[2]); - }, - transform: function() { - var a = arguments; - this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - translate: function() { - var a = arguments; - this._context.translate(a[0], a[1]); - }, - _enableTrace: function() { - var that = this, - len = CONTEXT_METHODS.length, - _simplifyArray = Konva.Util._simplifyArray, - origSetter = this.setAttr, - n, args; + // no support for IE9 and IE10 + }, + getLineDash: function() { + return this._context.getLineDash(); + }, + setTransform: function() { + var a = arguments; + this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + stroke: function() { + this._context.stroke(); + }, + strokeText: function() { + var a = arguments; + this._context.strokeText(a[0], a[1], a[2]); + }, + transform: function() { + var a = arguments; + this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + translate: function() { + var a = arguments; + this._context.translate(a[0], a[1]); + }, + _enableTrace: function() { + var that = this, + len = CONTEXT_METHODS.length, + _simplifyArray = Konva.Util._simplifyArray, + origSetter = this.setAttr, + n, + args; - // to prevent creating scope function at each loop - var func = function(methodName) { - var origMethod = that[methodName], - ret; + // to prevent creating scope function at each loop + var func = function(methodName) { + var origMethod = that[methodName], ret; - that[methodName] = function() { - args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); - ret = origMethod.apply(that, arguments); + that[methodName] = function() { + args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); + ret = origMethod.apply(that, arguments); - that._trace({ - method: methodName, - args: args - }); + that._trace({ + method: methodName, + args: args + }); - return ret; - }; - }; - // methods - for (n = 0; n < len; n++) { - func(CONTEXT_METHODS[n]); - } + return ret; + }; + }; + // methods + for (n = 0; n < len; n++) { + func(CONTEXT_METHODS[n]); + } - // attrs - that.setAttr = function() { - origSetter.apply(that, arguments); - var prop = arguments[0]; - var val = arguments[1]; - if ((prop === 'shadowOffsetX') || (prop === 'shadowOffsetY') || (prop === 'shadowBlur')) { - val = val / this.canvas.getPixelRatio(); - } - that._trace({ - property: prop, - val: val - }); - }; + // attrs + that.setAttr = function() { + origSetter.apply(that, arguments); + var prop = arguments[0]; + var val = arguments[1]; + if ( + prop === 'shadowOffsetX' || + prop === 'shadowOffsetY' || + prop === 'shadowBlur' + ) { + val = val / this.canvas.getPixelRatio(); } - }; - - CONTEXT_PROPERTIES.forEach(function(prop) { - Object.defineProperty(Konva.Context.prototype, prop, { - get: function () { - return this._context[prop]; - }, - set: function (val) { - this._context[prop] = val; - } + that._trace({ + property: prop, + val: val }); + }; + } + }; + + CONTEXT_PROPERTIES.forEach(function(prop) { + Object.defineProperty(Konva.Context.prototype, prop, { + get: function() { + return this._context[prop]; + }, + set: function(val) { + this._context[prop] = val; + } }); + }); - Konva.SceneContext = function(canvas) { - Konva.Context.call(this, canvas); - }; + Konva.SceneContext = function(canvas) { + Konva.Context.call(this, canvas); + }; - Konva.SceneContext.prototype = { - _fillColor: function(shape) { - var fill = shape.fill(); + Konva.SceneContext.prototype = { + _fillColor: function(shape) { + var fill = shape.fill(); - this.setAttr('fillStyle', fill); - shape._fillFunc(this); - }, - _fillPattern: function(shape) { - var fillPatternX = shape.getFillPatternX(), - fillPatternY = shape.getFillPatternY(), - fillPatternScale = shape.getFillPatternScale(), - fillPatternRotation = Konva.getAngle(shape.getFillPatternRotation()), - fillPatternOffset = shape.getFillPatternOffset(); + this.setAttr('fillStyle', fill); + shape._fillFunc(this); + }, + _fillPattern: function(shape) { + var fillPatternX = shape.getFillPatternX(), + fillPatternY = shape.getFillPatternY(), + fillPatternScale = shape.getFillPatternScale(), + fillPatternRotation = Konva.getAngle(shape.getFillPatternRotation()), + fillPatternOffset = shape.getFillPatternOffset(); - if(fillPatternX || fillPatternY) { - this.translate(fillPatternX || 0, fillPatternY || 0); - } - if(fillPatternRotation) { - this.rotate(fillPatternRotation); - } - if(fillPatternScale) { - this.scale(fillPatternScale.x, fillPatternScale.y); - } - if(fillPatternOffset) { - this.translate(-1 * fillPatternOffset.x, -1 * fillPatternOffset.y); - } + if (fillPatternX || fillPatternY) { + this.translate(fillPatternX || 0, fillPatternY || 0); + } + if (fillPatternRotation) { + this.rotate(fillPatternRotation); + } + if (fillPatternScale) { + this.scale(fillPatternScale.x, fillPatternScale.y); + } + if (fillPatternOffset) { + this.translate(-1 * fillPatternOffset.x, -1 * fillPatternOffset.y); + } - this.setAttr('fillStyle', this.createPattern(shape.getFillPatternImage(), shape.getFillPatternRepeat() || 'repeat')); - this.fill(); - }, - _fillLinearGradient: function(shape) { - var start = shape.getFillLinearGradientStartPoint(), - end = shape.getFillLinearGradientEndPoint(), - colorStops = shape.getFillLinearGradientColorStops(), - grd = this.createLinearGradient(start.x, start.y, end.x, end.y); + this.setAttr( + 'fillStyle', + this.createPattern( + shape.getFillPatternImage(), + shape.getFillPatternRepeat() || 'repeat' + ) + ); + this.fill(); + }, + _fillLinearGradient: function(shape) { + var start = shape.getFillLinearGradientStartPoint(), + end = shape.getFillLinearGradientEndPoint(), + colorStops = shape.getFillLinearGradientColorStops(), + grd = this.createLinearGradient(start.x, start.y, end.x, end.y); - if (colorStops) { - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - this.setAttr('fillStyle', grd); - shape._fillFunc(this); - } - }, - _fillRadialGradient: function(shape) { - var start = shape.getFillRadialGradientStartPoint(), - end = shape.getFillRadialGradientEndPoint(), - startRadius = shape.getFillRadialGradientStartRadius(), - endRadius = shape.getFillRadialGradientEndRadius(), - colorStops = shape.getFillRadialGradientColorStops(), - grd = this.createRadialGradient(start.x, start.y, startRadius, end.x, end.y, endRadius); - - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - this.setAttr('fillStyle', grd); - this.fill(); - }, - _fill: function(shape) { - var hasColor = shape.fill(), - hasPattern = shape.getFillPatternImage(), - hasLinearGradient = shape.getFillLinearGradientColorStops(), - hasRadialGradient = shape.getFillRadialGradientColorStops(), - fillPriority = shape.getFillPriority(); - - // priority fills - if(hasColor && fillPriority === 'color') { - this._fillColor(shape); - } - else if(hasPattern && fillPriority === 'pattern') { - this._fillPattern(shape); - } - else if(hasLinearGradient && fillPriority === 'linear-gradient') { - this._fillLinearGradient(shape); - } - else if(hasRadialGradient && fillPriority === 'radial-gradient') { - this._fillRadialGradient(shape); - } - // now just try and fill with whatever is available - else if(hasColor) { - this._fillColor(shape); - } - else if(hasPattern) { - this._fillPattern(shape); - } - else if(hasLinearGradient) { - this._fillLinearGradient(shape); - } - else if(hasRadialGradient) { - this._fillRadialGradient(shape); - } - }, - _stroke: function(shape) { - var dash = shape.dash(), - // ignore strokeScaleEnabled for Text - strokeScaleEnabled = (shape.getStrokeScaleEnabled() || (shape instanceof Konva.Text)); - - if(shape.hasStroke()) { - if (!strokeScaleEnabled) { - this.save(); - this.setTransform(1, 0, 0, 1, 0, 0); - } - - this._applyLineCap(shape); - if(dash && shape.dashEnabled()) { - this.setLineDash(dash); - } - - this.setAttr('lineWidth', shape.strokeWidth()); - this.setAttr('strokeStyle', shape.stroke()); - - if (!shape.getShadowForStrokeEnabled()) { - this.setAttr('shadowColor', 'rgba(0,0,0,0)'); - } - shape._strokeFunc(this); - - if (!strokeScaleEnabled) { - this.restore(); - } - } - }, - _applyShadow: function(shape) { - var util = Konva.Util, - color = util.get(shape.getShadowRGBA(), 'black'), - blur = util.get(shape.getShadowBlur(), 5), - offset = util.get(shape.getShadowOffset(), { - x: 0, - y: 0 - }), - // TODO: get this info from transform?? - scale = shape.getAbsoluteScale(), - ratio = this.canvas.getPixelRatio(), - scaleX = scale.x * ratio, - scaleY = scale.y * ratio; - - this.setAttr('shadowColor', color); - this.setAttr('shadowBlur', blur * ratio * Math.min(scaleX, scaleY)); - this.setAttr('shadowOffsetX', offset.x * scaleX); - this.setAttr('shadowOffsetY', offset.y * scaleY); + if (colorStops) { + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); } - }; - Konva.Util.extend(Konva.SceneContext, Konva.Context); + this.setAttr('fillStyle', grd); + shape._fillFunc(this); + } + }, + _fillRadialGradient: function(shape) { + var start = shape.getFillRadialGradientStartPoint(), + end = shape.getFillRadialGradientEndPoint(), + startRadius = shape.getFillRadialGradientStartRadius(), + endRadius = shape.getFillRadialGradientEndRadius(), + colorStops = shape.getFillRadialGradientColorStops(), + grd = this.createRadialGradient( + start.x, + start.y, + startRadius, + end.x, + end.y, + endRadius + ); - Konva.HitContext = function(canvas) { - Konva.Context.call(this, canvas); - }; + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + this.setAttr('fillStyle', grd); + this.fill(); + }, + _fill: function(shape) { + var hasColor = shape.fill(), + hasPattern = shape.getFillPatternImage(), + hasLinearGradient = shape.getFillLinearGradientColorStops(), + hasRadialGradient = shape.getFillRadialGradientColorStops(), + fillPriority = shape.getFillPriority(); - Konva.HitContext.prototype = { - _fill: function(shape) { - this.save(); - this.setAttr('fillStyle', shape.colorKey); - shape._fillFuncHit(this); - this.restore(); - }, - _stroke: function(shape) { - if(shape.hasStroke() && shape.strokeHitEnabled()) { - // ignore strokeScaleEnabled for Text - var strokeScaleEnabled = (shape.getStrokeScaleEnabled() || (shape instanceof Konva.Text)); - if (!strokeScaleEnabled) { - this.save(); - this.setTransform(1, 0, 0, 1, 0, 0); - } - this._applyLineCap(shape); - this.setAttr('lineWidth', shape.strokeWidth()); - this.setAttr('strokeStyle', shape.colorKey); - shape._strokeFuncHit(this); - if (!strokeScaleEnabled) { - this.restore(); - } - } + // priority fills + if (hasColor && fillPriority === 'color') { + this._fillColor(shape); + } else if (hasPattern && fillPriority === 'pattern') { + this._fillPattern(shape); + } else if (hasLinearGradient && fillPriority === 'linear-gradient') { + this._fillLinearGradient(shape); + } else if (hasRadialGradient && fillPriority === 'radial-gradient') { + this._fillRadialGradient(shape); + } else if (hasColor) { + // now just try and fill with whatever is available + this._fillColor(shape); + } else if (hasPattern) { + this._fillPattern(shape); + } else if (hasLinearGradient) { + this._fillLinearGradient(shape); + } else if (hasRadialGradient) { + this._fillRadialGradient(shape); + } + }, + _stroke: function(shape) { + var dash = shape.dash(), + // ignore strokeScaleEnabled for Text + strokeScaleEnabled = + shape.getStrokeScaleEnabled() || shape instanceof Konva.Text; + + if (shape.hasStroke()) { + if (!strokeScaleEnabled) { + this.save(); + this.setTransform(1, 0, 0, 1, 0, 0); } - }; - Konva.Util.extend(Konva.HitContext, Konva.Context); + + this._applyLineCap(shape); + if (dash && shape.dashEnabled()) { + this.setLineDash(dash); + this.setAttr('lineDashOffset', shape.dashOffset()); + } + + this.setAttr('lineWidth', shape.strokeWidth()); + this.setAttr('strokeStyle', shape.stroke()); + + if (!shape.getShadowForStrokeEnabled()) { + this.setAttr('shadowColor', 'rgba(0,0,0,0)'); + } + shape._strokeFunc(this); + + if (!strokeScaleEnabled) { + this.restore(); + } + } + }, + _applyShadow: function(shape) { + var util = Konva.Util, + color = util.get(shape.getShadowRGBA(), 'black'), + blur = util.get(shape.getShadowBlur(), 5), + offset = util.get(shape.getShadowOffset(), { + x: 0, + y: 0 + }), + // TODO: get this info from transform?? + scale = shape.getAbsoluteScale(), + ratio = this.canvas.getPixelRatio(), + scaleX = scale.x * ratio, + scaleY = scale.y * ratio; + + this.setAttr('shadowColor', color); + this.setAttr( + 'shadowBlur', + blur * ratio * Math.min(Math.abs(scaleX), Math.abs(scaleY)) + ); + this.setAttr('shadowOffsetX', offset.x * scaleX); + this.setAttr('shadowOffsetY', offset.y * scaleY); + }, + _applyGlobalCompositeOperation: function(shape) { + var globalCompositeOperation = shape.getGlobalCompositeOperation(); + if (globalCompositeOperation !== 'source-over') { + this.setAttr('globalCompositeOperation', globalCompositeOperation); + } + } + }; + Konva.Util.extend(Konva.SceneContext, Konva.Context); + + Konva.HitContext = function(canvas) { + Konva.Context.call(this, canvas); + }; + + Konva.HitContext.prototype = { + _fill: function(shape) { + this.save(); + this.setAttr('fillStyle', shape.colorKey); + shape._fillFuncHit(this); + this.restore(); + }, + _stroke: function(shape) { + if (shape.hasStroke() && shape.strokeHitEnabled()) { + // ignore strokeScaleEnabled for Text + var strokeScaleEnabled = + shape.getStrokeScaleEnabled() || shape instanceof Konva.Text; + if (!strokeScaleEnabled) { + this.save(); + this.setTransform(1, 0, 0, 1, 0, 0); + } + this._applyLineCap(shape); + this.setAttr('lineWidth', shape.strokeWidth()); + this.setAttr('strokeStyle', shape.colorKey); + shape._strokeFuncHit(this); + if (!strokeScaleEnabled) { + this.restore(); + } + } + } + }; + Konva.Util.extend(Konva.HitContext, Konva.Context); })(); (function() { @@ -2273,50 +2329,45 @@ })(); (function(Konva) { - 'use strict'; - // CONSTANTS - var ABSOLUTE_OPACITY = 'absoluteOpacity', - ABSOLUTE_TRANSFORM = 'absoluteTransform', - ABSOLUTE_SCALE = 'absoluteScale', - CHANGE = 'Change', - CHILDREN = 'children', - DOT = '.', - EMPTY_STRING = '', - GET = 'get', - ID = 'id', - KONVA = 'konva', - LISTENING = 'listening', - MOUSEENTER = 'mouseenter', - MOUSELEAVE = 'mouseleave', - NAME = 'name', - SET = 'set', - SHAPE = 'Shape', - SPACE = ' ', - STAGE = 'stage', - TRANSFORM = 'transform', - UPPER_STAGE = 'Stage', - VISIBLE = 'visible', - CLONE_BLACK_LIST = ['id'], + 'use strict'; + // CONSTANTS + var ABSOLUTE_OPACITY = 'absoluteOpacity', + ABSOLUTE_TRANSFORM = 'absoluteTransform', + ABSOLUTE_SCALE = 'absoluteScale', + CHANGE = 'Change', + CHILDREN = 'children', + DOT = '.', + EMPTY_STRING = '', + GET = 'get', + ID = 'id', + KONVA = 'konva', + LISTENING = 'listening', + MOUSEENTER = 'mouseenter', + MOUSELEAVE = 'mouseleave', + NAME = 'name', + SET = 'set', + SHAPE = 'Shape', + SPACE = ' ', + STAGE = 'stage', + TRANSFORM = 'transform', + UPPER_STAGE = 'Stage', + VISIBLE = 'visible', + CLONE_BLACK_LIST = ['id'], + TRANSFORM_CHANGE_STR = [ + 'xChange.konva', + 'yChange.konva', + 'scaleXChange.konva', + 'scaleYChange.konva', + 'skewXChange.konva', + 'skewYChange.konva', + 'rotationChange.konva', + 'offsetXChange.konva', + 'offsetYChange.konva', + 'transformsEnabledChange.konva' + ].join(SPACE), + SCALE_CHANGE_STR = ['scaleXChange.konva', 'scaleYChange.konva'].join(SPACE); - TRANSFORM_CHANGE_STR = [ - 'xChange.konva', - 'yChange.konva', - 'scaleXChange.konva', - 'scaleYChange.konva', - 'skewXChange.konva', - 'skewYChange.konva', - 'rotationChange.konva', - 'offsetXChange.konva', - 'offsetYChange.konva', - 'transformsEnabledChange.konva' - ].join(SPACE), - - SCALE_CHANGE_STR = [ - 'scaleXChange.konva', - 'scaleYChange.konva' - ].join(SPACE); - - /** + /** * Node constructor. Nodes are entities that can be transformed, layered, * and have bound events. The stage, layers, groups, and shapes all extend Node. * @constructor @@ -2344,73 +2395,72 @@ * @param {Number} [config.dragDistance] * @param {Function} [config.dragBoundFunc] */ - Konva.Node = function(config) { - this._init(config); - }; + Konva.Node = function(config) { + this._init(config); + }; - Konva.Util.addMethods(Konva.Node, { - _init: function(config) { - var that = this; - this._id = Konva.idCounter++; - this.eventListeners = {}; - this.attrs = {}; - this._cache = {}; - this._filterUpToDate = false; - this._isUnderCache = false; - this.setAttrs(config); + Konva.Util.addMethods(Konva.Node, { + _init: function(config) { + var that = this; + this._id = Konva.idCounter++; + this.eventListeners = {}; + this.attrs = {}; + this._cache = {}; + this._filterUpToDate = false; + this._isUnderCache = false; + this.setAttrs(config); - // event bindings for cache handling - this.on(TRANSFORM_CHANGE_STR, function() { - this._clearCache(TRANSFORM); - that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - }); + // event bindings for cache handling + this.on(TRANSFORM_CHANGE_STR, function() { + this._clearCache(TRANSFORM); + that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }); - this.on(SCALE_CHANGE_STR, function() { - that._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - }); + this.on(SCALE_CHANGE_STR, function() { + that._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + }); - this.on('visibleChange.konva', function() { - that._clearSelfAndDescendantCache(VISIBLE); - }); - this.on('listeningChange.konva', function() { - that._clearSelfAndDescendantCache(LISTENING); - }); - this.on('opacityChange.konva', function() { - that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - }); - }, - _clearCache: function(attr){ - if (attr) { - delete this._cache[attr]; - } - else { - this._cache = {}; - } - }, - _getCache: function(attr, privateGetter){ - var cache = this._cache[attr]; + this.on('visibleChange.konva', function() { + that._clearSelfAndDescendantCache(VISIBLE); + }); + this.on('listeningChange.konva', function() { + that._clearSelfAndDescendantCache(LISTENING); + }); + this.on('opacityChange.konva', function() { + that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + }); + }, + _clearCache: function(attr) { + if (attr) { + delete this._cache[attr]; + } else { + this._cache = {}; + } + }, + _getCache: function(attr, privateGetter) { + var cache = this._cache[attr]; - // if not cached, we need to set it using the private getter method. - if (cache === undefined) { - this._cache[attr] = privateGetter.call(this); - } + // if not cached, we need to set it using the private getter method. + if (cache === undefined) { + this._cache[attr] = privateGetter.call(this); + } - return this._cache[attr]; - }, - /* + return this._cache[attr]; + }, + /* * when the logic for a cached result depends on ancestor propagation, use this * method to clear self and children cache */ - _clearSelfAndDescendantCache: function(attr) { - this._clearCache(attr); + _clearSelfAndDescendantCache: function(attr) { + this._clearCache(attr); - if (this.children) { - this.getChildren().each(function(node) { - node._clearSelfAndDescendantCache(attr); - }); - } - }, - /** + if (this.children) { + this.getChildren().each(function(node) { + node._clearSelfAndDescendantCache(attr); + }); + } + }, + /** * clear cached canvas * @method * @memberof Konva.Node.prototype @@ -2418,12 +2468,12 @@ * @example * node.clearCache(); */ - clearCache: function() { - delete this._cache.canvas; - this._filterUpToDate = false; - return this; - }, - /** + clearCache: function() { + delete this._cache.canvas; + this._filterUpToDate = false; + return this; + }, + /** * cache node to improve drawing performance, apply filters, or create more accurate * hit regions. For all basic shapes size of cache canvas will be automatically detected. * If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box @@ -2438,6 +2488,7 @@ * @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions. * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached * region for debugging purposes + * @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache. * @returns {Konva.Node} * @example * // cache a shape with the x,y position of the bounding box at the center and @@ -2464,92 +2515,91 @@ * drawBorder: true * }); */ - cache: function(config) { - var conf = config || {}, - rect = this.getClientRect(true), - width = conf.width || rect.width, - height = conf.height || rect.height, - pixelRatio = conf.pixelRatio, - x = conf.x || rect.x, - y = conf.y || rect.y, - offset = conf.offset || 0, - drawBorder = conf.drawBorder || false; + cache: function(config) { + var conf = config || {}, + rect = this.getClientRect(true), + width = conf.width || rect.width, + height = conf.height || rect.height, + pixelRatio = conf.pixelRatio, + x = conf.x || rect.x, + y = conf.y || rect.y, + offset = conf.offset || 0, + drawBorder = conf.drawBorder || false; - if (!width || !height) { - throw new Error('Width or height of caching configuration equals 0.'); - } + if (!width || !height) { + throw new Error('Width or height of caching configuration equals 0.'); + } - width += offset * 2; - height += offset * 2; + width += offset * 2; + height += offset * 2; - x -= offset; - y -= offset; + x -= offset; + y -= offset; + var cachedSceneCanvas = new Konva.SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height + }), + cachedFilterCanvas = new Konva.SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height + }), + cachedHitCanvas = new Konva.HitCanvas({ + pixelRatio: 1, + width: width, + height: height + }), + sceneContext = cachedSceneCanvas.getContext(), + hitContext = cachedHitCanvas.getContext(); - var cachedSceneCanvas = new Konva.SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height - }), - cachedFilterCanvas = new Konva.SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height - }), - cachedHitCanvas = new Konva.HitCanvas({ - pixelRatio: 1, - width: width, - height: height - }), - sceneContext = cachedSceneCanvas.getContext(), - hitContext = cachedHitCanvas.getContext(); + cachedHitCanvas.isCache = true; - cachedHitCanvas.isCache = true; + this.clearCache(); - this.clearCache(); + sceneContext.save(); + hitContext.save(); - sceneContext.save(); - hitContext.save(); + sceneContext.translate(-x, -y); + hitContext.translate(-x, -y); - sceneContext.translate(-x, -y); - hitContext.translate(-x, -y); + // extra flag to skip on getAbsolute opacity calc + this._isUnderCache = true; + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - // extra flag to skip on getAbsolute opacity calc - this._isUnderCache = true; - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + this.drawScene(cachedSceneCanvas, this, true); + this.drawHit(cachedHitCanvas, this, true); + this._isUnderCache = false; - this.drawScene(cachedSceneCanvas, this, true); - this.drawHit(cachedHitCanvas, this, true); - this._isUnderCache = false; + sceneContext.restore(); + hitContext.restore(); - sceneContext.restore(); - hitContext.restore(); + // this will draw a red border around the cached box for + // debugging purposes + if (drawBorder) { + sceneContext.save(); + sceneContext.beginPath(); + sceneContext.rect(0, 0, width, height); + sceneContext.closePath(); + sceneContext.setAttr('strokeStyle', 'red'); + sceneContext.setAttr('lineWidth', 5); + sceneContext.stroke(); + sceneContext.restore(); + } - // this will draw a red border around the cached box for - // debugging purposes - if (drawBorder) { - sceneContext.save(); - sceneContext.beginPath(); - sceneContext.rect(0, 0, width, height); - sceneContext.closePath(); - sceneContext.setAttr('strokeStyle', 'red'); - sceneContext.setAttr('lineWidth', 5); - sceneContext.stroke(); - sceneContext.restore(); - } + this._cache.canvas = { + scene: cachedSceneCanvas, + filter: cachedFilterCanvas, + hit: cachedHitCanvas, + x: x, + y: y + }; - this._cache.canvas = { - scene: cachedSceneCanvas, - filter: cachedFilterCanvas, - hit: cachedHitCanvas, - x: x, - y: y - }; - - return this; - }, - /** + return this; + }, + /** * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). * The rectangle position is relative to parent container. * @method @@ -2581,109 +2631,123 @@ * rect.getClientRect(); * // returns Object {x: -2, y: 46, width: 104, height: 208} */ - getClientRect: function() { - // abstract method - // redefine in Container and Shape - throw new Error('abstract "getClientRect" method call'); - }, - _transformedRect: function(rect) { - var points = [ - {x: rect.x, y: rect.y}, - {x: rect.x + rect.width, y: rect.y}, - {x: rect.x + rect.width, y: rect.y + rect.height}, - {x: rect.x, y: rect.y + rect.height} - ]; - var minX, minY, maxX, maxY; - var trans = this.getTransform(); - points.forEach(function(point) { - var transformed = trans.point(point); - if (minX === undefined) { - minX = maxX = transformed.x; - minY = maxY = transformed.y; - } - minX = Math.min(minX, transformed.x); - minY = Math.min(minY, transformed.y); - maxX = Math.max(maxX, transformed.x); - maxY = Math.max(maxY, transformed.y); - }); - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY - }; - }, - _drawCachedSceneCanvas: function(context) { - context.save(); - context._applyOpacity(this); - context.translate( - this._cache.canvas.x, - this._cache.canvas.y + getClientRect: function() { + // abstract method + // redefine in Container and Shape + throw new Error('abstract "getClientRect" method call'); + }, + _transformedRect: function(rect) { + var points = [ + { x: rect.x, y: rect.y }, + { x: rect.x + rect.width, y: rect.y }, + { x: rect.x + rect.width, y: rect.y + rect.height }, + { x: rect.x, y: rect.y + rect.height } + ]; + var minX, minY, maxX, maxY; + var trans = this.getTransform(); + points.forEach(function(point) { + var transformed = trans.point(point); + if (minX === undefined) { + minX = maxX = transformed.x; + minY = maxY = transformed.y; + } + minX = Math.min(minX, transformed.x); + minY = Math.min(minY, transformed.y); + maxX = Math.max(maxX, transformed.x); + maxY = Math.max(maxY, transformed.y); + }); + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY + }; + }, + _drawCachedSceneCanvas: function(context) { + context.save(); + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.translate(this._cache.canvas.x, this._cache.canvas.y); + + var cacheCanvas = this._getCachedSceneCanvas(); + var ratio = cacheCanvas.pixelRatio; + + context.drawImage( + cacheCanvas._canvas, + 0, + 0, + cacheCanvas.width / ratio, + cacheCanvas.height / ratio + ); + context.restore(); + }, + _drawCachedHitCanvas: function(context) { + var cachedCanvas = this._cache.canvas, hitCanvas = cachedCanvas.hit; + context.save(); + context.translate(this._cache.canvas.x, this._cache.canvas.y); + context.drawImage(hitCanvas._canvas, 0, 0); + context.restore(); + }, + _getCachedSceneCanvas: function() { + var filters = this.filters(), + cachedCanvas = this._cache.canvas, + sceneCanvas = cachedCanvas.scene, + filterCanvas = cachedCanvas.filter, + filterContext = filterCanvas.getContext(), + len, + imageData, + n, + filter; + + if (filters) { + if (!this._filterUpToDate) { + var ratio = sceneCanvas.pixelRatio; + + try { + len = filters.length; + filterContext.clear(); + + // copy cached canvas onto filter context + filterContext.drawImage( + sceneCanvas._canvas, + 0, + 0, + sceneCanvas.getWidth() / ratio, + sceneCanvas.getHeight() / ratio + ); + imageData = filterContext.getImageData( + 0, + 0, + filterCanvas.getWidth(), + filterCanvas.getHeight() ); - var cacheCanvas = this._getCachedSceneCanvas(); - var ratio = cacheCanvas.pixelRatio; - - context.drawImage(cacheCanvas._canvas, 0, 0, cacheCanvas.width / ratio, cacheCanvas.height / ratio); - context.restore(); - }, - _drawCachedHitCanvas: function(context) { - var cachedCanvas = this._cache.canvas, - hitCanvas = cachedCanvas.hit; - context.save(); - context.translate( - this._cache.canvas.x, - this._cache.canvas.y - ); - context.drawImage(hitCanvas._canvas, 0, 0); - context.restore(); - }, - _getCachedSceneCanvas: function() { - var filters = this.filters(), - cachedCanvas = this._cache.canvas, - sceneCanvas = cachedCanvas.scene, - filterCanvas = cachedCanvas.filter, - filterContext = filterCanvas.getContext(), - len, imageData, n, filter; - - if (filters) { - if (!this._filterUpToDate) { - var ratio = sceneCanvas.pixelRatio; - - try { - len = filters.length; - filterContext.clear(); - - // copy cached canvas onto filter context - filterContext.drawImage(sceneCanvas._canvas, 0, 0, sceneCanvas.getWidth() / ratio, sceneCanvas.getHeight() / ratio); - imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight()); - - // apply filters to filter context - for (n = 0; n < len; n++) { - filter = filters[n]; - if (typeof filter !== 'function') { - Konva.Util.error( - 'Filter should be type of function, but got ' + - (typeof filter) + ' insted. Please check correct filters' - ); - continue; - } - filter.call(this, imageData); - filterContext.putImageData(imageData, 0, 0); - } - } - catch(e) { - Konva.Util.error('Unable to apply filter. ' + e.message); - } - - this._filterUpToDate = true; - } - - return filterCanvas; + // apply filters to filter context + for (n = 0; n < len; n++) { + filter = filters[n]; + if (typeof filter !== 'function') { + Konva.Util.error( + 'Filter should be type of function, but got ' + + typeof filter + + ' insted. Please check correct filters' + ); + continue; + } + filter.call(this, imageData); + filterContext.putImageData(imageData, 0, 0); } - return sceneCanvas; - }, - /** + } catch (e) { + Konva.Util.error('Unable to apply filter. ' + e.message); + } + + this._filterUpToDate = true; + } + + return filterCanvas; + } + return sceneCanvas; + }, + /** * bind events to the node. KonvaJS supports mouseover, mousemove, * mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, click, dblclick, touchstart, touchmove, * touchend, tap, dbltap, dragstart, dragmove, and dragend events. The Konva Stage supports @@ -2746,39 +2810,43 @@ * var group = evtn.currentTarger; * }); */ - on: function(evtStr, handler) { - if (arguments.length === 3) { - return this._delegate.apply(this, arguments); - } - var events = evtStr.split(SPACE), - len = events.length, - n, event, parts, baseEvent, name; + on: function(evtStr, handler) { + if (arguments.length === 3) { + return this._delegate.apply(this, arguments); + } + var events = evtStr.split(SPACE), + len = events.length, + n, + event, + parts, + baseEvent, + name; - /* + /* * loop through types and attach event listeners to * each one. eg. 'click mouseover.namespace mouseout' * will create three event bindings */ - for(n = 0; n < len; n++) { - event = events[n]; - parts = event.split(DOT); - baseEvent = parts[0]; - name = parts[1] || EMPTY_STRING; + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1] || EMPTY_STRING; - // create events array if it doesn't exist - if(!this.eventListeners[baseEvent]) { - this.eventListeners[baseEvent] = []; - } + // create events array if it doesn't exist + if (!this.eventListeners[baseEvent]) { + this.eventListeners[baseEvent] = []; + } - this.eventListeners[baseEvent].push({ - name: name, - handler: handler - }); - } + this.eventListeners[baseEvent].push({ + name: name, + handler: handler + }); + } - return this; - }, - /** + return this; + }, + /** * remove event bindings from the node. Pass in a string of * event types delimmited by a space to remove multiple event * bindings at once such as 'mousedown mouseup mousemove'. @@ -2799,70 +2867,74 @@ * // remove listener by name * node.off('click.foo'); */ - off: function(evtStr) { - var events = (evtStr || '').split(SPACE), - len = events.length, - n, t, event, parts, baseEvent, name; + off: function(evtStr) { + var events = (evtStr || '').split(SPACE), + len = events.length, + n, + t, + event, + parts, + baseEvent, + name; - if (!evtStr) { - // remove all events - for(t in this.eventListeners) { - this._off(t); - } - } - for(n = 0; n < len; n++) { - event = events[n]; - parts = event.split(DOT); - baseEvent = parts[0]; - name = parts[1]; + if (!evtStr) { + // remove all events + for (t in this.eventListeners) { + this._off(t); + } + } + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1]; - if(baseEvent) { - if(this.eventListeners[baseEvent]) { - this._off(baseEvent, name); - } - } - else { - for(t in this.eventListeners) { - this._off(t, name); - } - } - } - return this; - }, - // some event aliases for third party integration like HammerJS - dispatchEvent: function(evt) { - var e = { - target: this, - type: evt.type, - evt: evt - }; - this.fire(evt.type, e); - return this; - }, - addEventListener: function(type, handler) { - // we have to pass native event to handler - this.on(type, function(evt){ - handler.call(this, evt.evt); - }); - return this; - }, - removeEventListener: function(type) { - this.off(type); - return this; - }, - // like node.on - _delegate: function(event, selector, handler) { - var stopNode = this; - this.on(event, function(evt) { - var targets = evt.target.findAncestors(selector, true, stopNode); - for(var i = 0; i < targets.length; i++) { - evt = Konva.Util.cloneObject(evt); - evt.currentTarget = targets[i]; - handler.call(targets[i], evt); - } - }); - }, - /** + if (baseEvent) { + if (this.eventListeners[baseEvent]) { + this._off(baseEvent, name); + } + } else { + for (t in this.eventListeners) { + this._off(t, name); + } + } + } + return this; + }, + // some event aliases for third party integration like HammerJS + dispatchEvent: function(evt) { + var e = { + target: this, + type: evt.type, + evt: evt + }; + this.fire(evt.type, e); + return this; + }, + addEventListener: function(type, handler) { + // we have to pass native event to handler + this.on(type, function(evt) { + handler.call(this, evt.evt); + }); + return this; + }, + removeEventListener: function(type) { + this.off(type); + return this; + }, + // like node.on + _delegate: function(event, selector, handler) { + var stopNode = this; + this.on(event, function(evt) { + var targets = evt.target.findAncestors(selector, true, stopNode); + for (var i = 0; i < targets.length; i++) { + evt = Konva.Util.cloneObject(evt); + evt.currentTarget = targets[i]; + handler.call(targets[i], evt); + } + }); + }, + /** * remove self from parent, but don't destroy * @method * @memberof Konva.Node.prototype @@ -2870,47 +2942,47 @@ * @example * node.remove(); */ - remove: function() { - var parent = this.getParent(); + remove: function() { + var parent = this.getParent(); - if(parent && parent.children) { - parent.children.splice(this.index, 1); - parent._setChildrenIndices(); - delete this.parent; - } + if (parent && parent.children) { + parent.children.splice(this.index, 1); + parent._setChildrenIndices(); + delete this.parent; + } - // every cached attr that is calculated via node tree - // traversal must be cleared when removing a node - this._clearSelfAndDescendantCache(STAGE); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - this._clearSelfAndDescendantCache(VISIBLE); - this._clearSelfAndDescendantCache(LISTENING); - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + // every cached attr that is calculated via node tree + // traversal must be cleared when removing a node + this._clearSelfAndDescendantCache(STAGE); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearSelfAndDescendantCache(VISIBLE); + this._clearSelfAndDescendantCache(LISTENING); + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - return this; - }, - /** + return this; + }, + /** * remove and destroy self * @method * @memberof Konva.Node.prototype * @example * node.destroy(); */ - destroy: function() { - // remove from ids and names hashes - Konva._removeId(this.getId()); + destroy: function() { + // remove from ids and names hashes + Konva._removeId(this.getId()); - // remove all names - var names = (this.getName() || '').split(/\s/g); - for(var i = 0; i < names.length; i++) { - var subname = names[i]; - Konva._removeName(subname, this._id); - } + // remove all names + var names = (this.getName() || '').split(/\s/g); + for (var i = 0; i < names.length; i++) { + var subname = names[i]; + Konva._removeName(subname, this._id); + } - this.remove(); - return this; - }, - /** + this.remove(); + return this; + }, + /** * get attr * @method * @memberof Konva.Node.prototype @@ -2919,15 +2991,15 @@ * @example * var x = node.getAttr('x'); */ - getAttr: function(attr) { - var method = GET + Konva.Util._capitalize(attr); - if(Konva.Util._isFunction(this[method])) { - return this[method](); - } - // otherwise get directly - return this.attrs[attr]; - }, - /** + getAttr: function(attr) { + var method = GET + Konva.Util._capitalize(attr); + if (Konva.Util._isFunction(this[method])) { + return this[method](); + } + // otherwise get directly + return this.attrs[attr]; + }, + /** * get ancestors * @method * @memberof Konva.Node.prototype @@ -2937,27 +3009,26 @@ * console.log(node.getId()); * }) */ - getAncestors: function() { - var parent = this.getParent(), - ancestors = new Konva.Collection(); + getAncestors: function() { + var parent = this.getParent(), ancestors = new Konva.Collection(); - while (parent) { - ancestors.push(parent); - parent = parent.getParent(); - } + while (parent) { + ancestors.push(parent); + parent = parent.getParent(); + } - return ancestors; - }, - /** + return ancestors; + }, + /** * get attrs object literal * @method * @memberof Konva.Node.prototype * @returns {Object} */ - getAttrs: function() { - return this.attrs || {}; - }, - /** + getAttrs: function() { + return this.attrs || {}; + }, + /** * set multiple attrs at once using an object literal * @method * @memberof Konva.Node.prototype @@ -2969,29 +3040,28 @@ * fill: 'red' * }); */ - setAttrs: function(config) { - var key, method; + setAttrs: function(config) { + var key, method; - if(!config) { - return this; - } - for(key in config) { - if (key === CHILDREN) { - continue; - } - method = SET + Konva.Util._capitalize(key); - // use setter if available - if(Konva.Util._isFunction(this[method])) { - this[method](config[key]); - } - // otherwise set directly - else { - this._setAttr(key, config[key]); - } - } - return this; - }, - /** + if (!config) { + return this; + } + for (key in config) { + if (key === CHILDREN) { + continue; + } + method = SET + Konva.Util._capitalize(key); + // use setter if available + if (Konva.Util._isFunction(this[method])) { + this[method](config[key]); + } else { + // otherwise set directly + this._setAttr(key, config[key]); + } + } + return this; + }, + /** * determine if node is listening for events by taking into account ancestors. * * Parent | Self | isListening @@ -3010,28 +3080,25 @@ * @memberof Konva.Node.prototype * @returns {Boolean} */ - isListening: function() { - return this._getCache(LISTENING, this._isListening); - }, - _isListening: function() { - var listening = this.getListening(), - parent = this.getParent(); + isListening: function() { + return this._getCache(LISTENING, this._isListening); + }, + _isListening: function() { + var listening = this.getListening(), parent = this.getParent(); - // the following conditions are a simplification of the truth table above. - // please modify carefully - if (listening === 'inherit') { - if (parent) { - return parent.isListening(); - } - else { - return true; - } - } - else { - return listening; - } - }, - /** + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (listening === 'inherit') { + if (parent) { + return parent.isListening(); + } else { + return true; + } + } else { + return listening; + } + }, + /** * determine if node is visible by taking into account ancestors. * * Parent | Self | isVisible @@ -3050,108 +3117,107 @@ * @memberof Konva.Node.prototype * @returns {Boolean} */ - isVisible: function() { - return this._getCache(VISIBLE, this._isVisible); - }, - _isVisible: function() { - var visible = this.getVisible(), - parent = this.getParent(); + isVisible: function() { + return this._getCache(VISIBLE, this._isVisible); + }, + _isVisible: function() { + var visible = this.getVisible(), parent = this.getParent(); - // the following conditions are a simplification of the truth table above. - // please modify carefully - if (visible === 'inherit') { - if (parent) { - return parent.isVisible(); - } - else { - return true; - } - } - else { - return visible; - } - }, - /** + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (visible === 'inherit') { + if (parent) { + return parent.isVisible(); + } else { + return true; + } + } else { + return visible; + } + }, + /** * determine if listening is enabled by taking into account descendants. If self or any children * have _isListeningEnabled set to true, then self also has listening enabled. * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - shouldDrawHit: function(canvas) { - var layer = this.getLayer(); - return (canvas && canvas.isCache) || (layer && layer.hitGraphEnabled()) - && this.isListening() && this.isVisible(); - }, - /** + shouldDrawHit: function(canvas) { + var layer = this.getLayer(); + return ( + (canvas && canvas.isCache) || + (layer && + layer.hitGraphEnabled() && + this.isListening() && + this.isVisible()) + ); + }, + /** * show node * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - show: function() { - this.setVisible(true); - return this; - }, - /** + show: function() { + this.setVisible(true); + return this; + }, + /** * hide node. Hidden nodes are no longer detectable * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - hide: function() { - this.setVisible(false); - return this; - }, - /** + hide: function() { + this.setVisible(false); + return this; + }, + /** * get zIndex relative to the node's siblings who share the same parent * @method * @memberof Konva.Node.prototype * @returns {Integer} */ - getZIndex: function() { - return this.index || 0; - }, - /** + getZIndex: function() { + return this.index || 0; + }, + /** * get absolute z-index which takes into account sibling * and ancestor indices * @method * @memberof Konva.Node.prototype * @returns {Integer} */ - getAbsoluteZIndex: function() { - var depth = this.getDepth(), - that = this, - index = 0, - nodes, len, n, child; + getAbsoluteZIndex: function() { + var depth = this.getDepth(), that = this, index = 0, nodes, len, n, child; - function addChildren(children) { - nodes = []; - len = children.length; - for(n = 0; n < len; n++) { - child = children[n]; - index++; + function addChildren(children) { + nodes = []; + len = children.length; + for (n = 0; n < len; n++) { + child = children[n]; + index++; - if(child.nodeType !== SHAPE) { - nodes = nodes.concat(child.getChildren().toArray()); - } + if (child.nodeType !== SHAPE) { + nodes = nodes.concat(child.getChildren().toArray()); + } - if(child._id === that._id) { - n = len; - } - } + if (child._id === that._id) { + n = len; + } + } - if(nodes.length > 0 && nodes[0].getDepth() <= depth) { - addChildren(nodes); - } - } - if(that.nodeType !== UPPER_STAGE) { - addChildren(that.getStage().getChildren()); - } + if (nodes.length > 0 && nodes[0].getDepth() <= depth) { + addChildren(nodes); + } + } + if (that.nodeType !== UPPER_STAGE) { + addChildren(that.getStage().getChildren()); + } - return index; - }, - /** + return index; + }, + /** * get node depth in node tree. Returns an integer. * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always * be >= 2 @@ -3159,28 +3225,27 @@ * @memberof Konva.Node.prototype * @returns {Integer} */ - getDepth: function() { - var depth = 0, - parent = this.parent; + getDepth: function() { + var depth = 0, parent = this.parent; - while(parent) { - depth++; - parent = parent.parent; - } - return depth; - }, - setPosition: function(pos) { - this.setX(pos.x); - this.setY(pos.y); - return this; - }, - getPosition: function() { - return { - x: this.getX(), - y: this.getY() - }; - }, - /** + while (parent) { + depth++; + parent = parent.parent; + } + return depth; + }, + setPosition: function(pos) { + this.setX(pos.x); + this.setY(pos.y); + return this; + }, + getPosition: function() { + return { + x: this.getX(), + y: this.getY() + }; + }, + /** * get absolute position relative to the top left corner of the stage container div * or relative to passed node * @method @@ -3188,18 +3253,18 @@ * @memberof Konva.Node.prototype * @returns {Object} */ - getAbsolutePosition: function(top) { - var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), - absoluteTransform = new Konva.Transform(), - offset = this.offset(); + getAbsolutePosition: function(top) { + var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), + absoluteTransform = new Konva.Transform(), + offset = this.offset(); - // clone the matrix array - absoluteTransform.m = absoluteMatrix.slice(); - absoluteTransform.translate(offset.x, offset.y); + // clone the matrix array + absoluteTransform.m = absoluteMatrix.slice(); + absoluteTransform.translate(offset.x, offset.y); - return absoluteTransform.getTranslation(); - }, - /** + return absoluteTransform.getTranslation(); + }, + /** * set absolute position * @method * @memberof Konva.Node.prototype @@ -3208,71 +3273,70 @@ * @param {Number} pos.y * @returns {Konva.Node} */ - setAbsolutePosition: function(pos) { - var origTrans = this._clearTransform(), - it; + setAbsolutePosition: function(pos) { + var origTrans = this._clearTransform(), it; - // don't clear translation - this.attrs.x = origTrans.x; - this.attrs.y = origTrans.y; - delete origTrans.x; - delete origTrans.y; + // don't clear translation + this.attrs.x = origTrans.x; + this.attrs.y = origTrans.y; + delete origTrans.x; + delete origTrans.y; - // unravel transform - it = this.getAbsoluteTransform(); + // unravel transform + it = this.getAbsoluteTransform(); - it.invert(); - it.translate(pos.x, pos.y); - pos = { - x: this.attrs.x + it.getTranslation().x, - y: this.attrs.y + it.getTranslation().y - }; + it.invert(); + it.translate(pos.x, pos.y); + pos = { + x: this.attrs.x + it.getTranslation().x, + y: this.attrs.y + it.getTranslation().y + }; - this.setPosition({x: pos.x, y: pos.y}); - this._setTransform(origTrans); + this.setPosition({ x: pos.x, y: pos.y }); + this._setTransform(origTrans); - return this; - }, - _setTransform: function(trans) { - var key; + return this; + }, + _setTransform: function(trans) { + var key; - for(key in trans) { - this.attrs[key] = trans[key]; - } + for (key in trans) { + this.attrs[key] = trans[key]; + } - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - }, - _clearTransform: function() { - var trans = { - x: this.getX(), - y: this.getY(), - rotation: this.getRotation(), - scaleX: this.getScaleX(), - scaleY: this.getScaleY(), - offsetX: this.getOffsetX(), - offsetY: this.getOffsetY(), - skewX: this.getSkewX(), - skewY: this.getSkewY() - }; + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }, + _clearTransform: function() { + var trans = { + x: this.getX(), + y: this.getY(), + rotation: this.getRotation(), + scaleX: this.getScaleX(), + scaleY: this.getScaleY(), + offsetX: this.getOffsetX(), + offsetY: this.getOffsetY(), + skewX: this.getSkewX(), + skewY: this.getSkewY() + }; - this.attrs.x = 0; - this.attrs.y = 0; - this.attrs.rotation = 0; - this.attrs.scaleX = 1; - this.attrs.scaleY = 1; - this.attrs.offsetX = 0; - this.attrs.offsetY = 0; - this.attrs.skewX = 0; - this.attrs.skewY = 0; + this.attrs.x = 0; + this.attrs.y = 0; + this.attrs.rotation = 0; + this.attrs.scaleX = 1; + this.attrs.scaleY = 1; + this.attrs.offsetX = 0; + this.attrs.offsetY = 0; + this.attrs.skewX = 0; + this.attrs.skewY = 0; - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - // return original transform - return trans; - }, - /** + // return original transform + return trans; + }, + /** * move node by an amount relative to its current position * @method * @memberof Konva.Node.prototype @@ -3287,173 +3351,172 @@ * y: 2) * }); */ - move: function(change) { - var changeX = change.x, - changeY = change.y, - x = this.getX(), - y = this.getY(); + move: function(change) { + var changeX = change.x, + changeY = change.y, + x = this.getX(), + y = this.getY(); - if(changeX !== undefined) { - x += changeX; - } + if (changeX !== undefined) { + x += changeX; + } - if(changeY !== undefined) { - y += changeY; - } + if (changeY !== undefined) { + y += changeY; + } - this.setPosition({x: x, y: y}); - return this; - }, - _eachAncestorReverse: function(func, top) { - var family = [], - parent = this.getParent(), - len, n; + this.setPosition({ x: x, y: y }); + return this; + }, + _eachAncestorReverse: function(func, top) { + var family = [], parent = this.getParent(), len, n; - // if top node is defined, and this node is top node, - // there's no need to build a family tree. just execute - // func with this because it will be the only node - if (top && top._id === this._id) { - func(this); - return true; - } + // if top node is defined, and this node is top node, + // there's no need to build a family tree. just execute + // func with this because it will be the only node + if (top && top._id === this._id) { + func(this); + return true; + } - family.unshift(this); + family.unshift(this); - while(parent && (!top || parent._id !== top._id)) { - family.unshift(parent); - parent = parent.parent; - } + while (parent && (!top || parent._id !== top._id)) { + family.unshift(parent); + parent = parent.parent; + } - len = family.length; - for(n = 0; n < len; n++) { - func(family[n]); - } - }, - /** + len = family.length; + for (n = 0; n < len; n++) { + func(family[n]); + } + }, + /** * rotate node by an amount in degrees relative to its current rotation * @method * @memberof Konva.Node.prototype * @param {Number} theta * @returns {Konva.Node} */ - rotate: function(theta) { - this.setRotation(this.getRotation() + theta); - return this; - }, - /** + rotate: function(theta) { + this.setRotation(this.getRotation() + theta); + return this; + }, + /** * move node to the top of its siblings * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - moveToTop: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); - return false; - } - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.push(this); - this.parent._setChildrenIndices(); - return true; - }, - /** + moveToTop: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); + return false; + } + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.push(this); + this.parent._setChildrenIndices(); + return true; + }, + /** * move node up * @method * @memberof Konva.Node.prototype * @returns {Boolean} flag is moved or not */ - moveUp: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveUp function is ignored.'); - return false; - } - var index = this.index, - len = this.parent.getChildren().length; - if(index < len - 1) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index + 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** + moveUp: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveUp function is ignored.'); + return false; + } + var index = this.index, len = this.parent.getChildren().length; + if (index < len - 1) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index + 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** * move node down * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - moveDown: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveDown function is ignored.'); - return false; - } - var index = this.index; - if(index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index - 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** + moveDown: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveDown function is ignored.'); + return false; + } + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index - 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** * move node to the bottom of its siblings * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - moveToBottom: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveToBottom function is ignored.'); - return false; - } - var index = this.index; - if(index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.unshift(this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** + moveToBottom: function() { + if (!this.parent) { + Konva.Util.warn( + 'Node has no parent. moveToBottom function is ignored.' + ); + return false; + } + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.unshift(this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** * set zIndex relative to siblings * @method * @memberof Konva.Node.prototype * @param {Integer} zIndex * @returns {Konva.Node} */ - setZIndex: function(zIndex) { - if (!this.parent) { - Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); - return false; - } - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.splice(zIndex, 0, this); - this.parent._setChildrenIndices(); - return this; - }, - /** + setZIndex: function(zIndex) { + if (!this.parent) { + Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); + return false; + } + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(zIndex, 0, this); + this.parent._setChildrenIndices(); + return this; + }, + /** * get absolute opacity * @method * @memberof Konva.Node.prototype * @returns {Number} */ - getAbsoluteOpacity: function() { - return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); - }, - _getAbsoluteOpacity: function() { - var absOpacity = this.getOpacity(); - var parent = this.getParent(); - if(parent && !parent._isUnderCache) { - absOpacity *= this.getParent().getAbsoluteOpacity(); - } - return absOpacity; - }, - /** + getAbsoluteOpacity: function() { + return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); + }, + _getAbsoluteOpacity: function() { + var absOpacity = this.getOpacity(); + var parent = this.getParent(); + if (parent && !parent._isUnderCache) { + absOpacity *= this.getParent().getAbsoluteOpacity(); + } + return absOpacity; + }, + /** * move node to another container * @method * @memberof Konva.Node.prototype @@ -3463,64 +3526,62 @@ * // move node from current layer into layer2 * node.moveTo(layer2); */ - moveTo: function(newContainer) { - // do nothing if new container is already parent - if (this.getParent() !== newContainer) { - // this.remove my be overrided by drag and drop - // buy we need original - (this.__originalRemove || this.remove).call(this); - newContainer.add(this); - } - return this; - }, - /** + moveTo: function(newContainer) { + // do nothing if new container is already parent + if (this.getParent() !== newContainer) { + // this.remove my be overrided by drag and drop + // buy we need original + (this.__originalRemove || this.remove).call(this); + newContainer.add(this); + } + return this; + }, + /** * convert Node into an object for serialization. Returns an object. * @method * @memberof Konva.Node.prototype * @returns {Object} */ - toObject: function() { - var obj = {}, - attrs = this.getAttrs(), - key, val, getter, defaultValue; + toObject: function() { + var obj = {}, attrs = this.getAttrs(), key, val, getter, defaultValue; - obj.attrs = {}; + obj.attrs = {}; - for(key in attrs) { - val = attrs[key]; - getter = this[key]; - // remove attr value so that we can extract the default value from the getter - delete attrs[key]; - defaultValue = getter ? getter.call(this) : null; - // restore attr value - attrs[key] = val; - if (defaultValue !== val) { - obj.attrs[key] = val; - } - } + for (key in attrs) { + val = attrs[key]; + getter = this[key]; + // remove attr value so that we can extract the default value from the getter + delete attrs[key]; + defaultValue = getter ? getter.call(this) : null; + // restore attr value + attrs[key] = val; + if (defaultValue !== val) { + obj.attrs[key] = val; + } + } - obj.className = this.getClassName(); - return Konva.Util._prepareToStringify(obj); - }, - /** + obj.className = this.getClassName(); + return Konva.Util._prepareToStringify(obj); + }, + /** * convert Node into a JSON string. Returns a JSON string. * @method * @memberof Konva.Node.prototype * @returns {String}} */ - toJSON: function() { - return JSON.stringify(this.toObject()); - }, - /** + toJSON: function() { + return JSON.stringify(this.toObject()); + }, + /** * get parent container * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - getParent: function() { - return this.parent; - }, - /** + getParent: function() { + return this.parent; + }, + /** * get all ancestros (parent then parent of the parent, etc) of the node * @method * @memberof Konva.Node.prototype @@ -3532,25 +3593,25 @@ * // get one of the parent group * var parentGroups = node.findAncestors('Group'); */ - findAncestors: function(selector, includeSelf, stopNode) { - var res = []; + findAncestors: function(selector, includeSelf, stopNode) { + var res = []; - if (includeSelf && this._isMatch(selector)) { - res.push(this); - } - var ancestor = this.parent; - while(ancestor) { - if (ancestor === stopNode) { - return res; - } - if (ancestor._isMatch(selector)) { - res.push(ancestor); - } - ancestor = ancestor.parent; - } - return res; - }, - /** + if (includeSelf && this._isMatch(selector)) { + res.push(this); + } + var ancestor = this.parent; + while (ancestor) { + if (ancestor === stopNode) { + return res; + } + if (ancestor._isMatch(selector)) { + res.push(ancestor); + } + ancestor = ancestor.parent; + } + return res; + }, + /** * get ancestor (parent or parent of the parent, etc) of the node that match passed selector * @method * @memberof Konva.Node.prototype @@ -3562,71 +3623,76 @@ * // get one of the parent group * var group = node.findAncestors('.mygroup'); */ - findAncestor: function(selector, includeSelf, stopNode) { - return this.findAncestors(selector, includeSelf, stopNode)[0]; - }, - // is current node match passed selector? - _isMatch: function(selector) { - if (!selector) { - return false; - } - var selectorArr = selector.replace(/ /g, '').split(','), - len = selectorArr.length, - n, sel; + findAncestor: function(selector, includeSelf, stopNode) { + return this.findAncestors(selector, includeSelf, stopNode)[0]; + }, + // is current node match passed selector? + _isMatch: function(selector) { + if (!selector) { + return false; + } + var selectorArr = selector.replace(/ /g, '').split(','), + len = selectorArr.length, + n, + sel; - for (n = 0; n < len; n++) { - sel = selectorArr[n]; - if (!Konva.Util.isValidSelector(sel)) { - Konva.Util.warn('Selector "' + sel + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".'); - Konva.Util.warn('If you have a custom shape with such className, please change it to start with upper letter like "Triangle".'); - Konva.Util.warn('Konva is awesome, right?'); - } - // id selector - if(sel.charAt(0) === '#') { - if (this.id() === sel.slice(1)) { - return true; - } - } - // name selector - else if(sel.charAt(0) === '.') { - if (this.hasName(sel.slice(1))) { - return true; - } - } else if (this._get(sel).length !== 0) { - return true; - } - } - return false; - }, - /** + for (n = 0; n < len; n++) { + sel = selectorArr[n]; + if (!Konva.Util.isValidSelector(sel)) { + Konva.Util.warn( + 'Selector "' + + sel + + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' + ); + Konva.Util.warn( + 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' + ); + Konva.Util.warn('Konva is awesome, right?'); + } + // id selector + if (sel.charAt(0) === '#') { + if (this.id() === sel.slice(1)) { + return true; + } + } else if (sel.charAt(0) === '.') { + // name selector + if (this.hasName(sel.slice(1))) { + return true; + } + } else if (this._get(sel).length !== 0) { + return true; + } + } + return false; + }, + /** * get layer ancestor * @method * @memberof Konva.Node.prototype * @returns {Konva.Layer} */ - getLayer: function() { - var parent = this.getParent(); - return parent ? parent.getLayer() : null; - }, - /** + getLayer: function() { + var parent = this.getParent(); + return parent ? parent.getLayer() : null; + }, + /** * get stage ancestor * @method * @memberof Konva.Node.prototype * @returns {Konva.Stage} */ - getStage: function() { - return this._getCache(STAGE, this._getStage); - }, - _getStage: function() { - var parent = this.getParent(); - if(parent) { - return parent.getStage(); - } - else { - return undefined; - } - }, - /** + getStage: function() { + return this._getCache(STAGE, this._getStage); + }, + _getStage: function() { + var parent = this.getParent(); + if (parent) { + return parent.getStage(); + } else { + return undefined; + } + }, + /** * fire event * @method * @memberof Konva.Node.prototype @@ -3650,134 +3716,128 @@ * // fire click event that bubbles * node.fire('click', null, true); */ - fire: function(eventType, evt, bubble) { - evt = evt || {}; - evt.target = evt.target || this; - // bubble - if (bubble) { - this._fireAndBubble(eventType, evt); - } - // no bubble - else { - this._fire(eventType, evt); - } - return this; - }, - /** + fire: function(eventType, evt, bubble) { + evt = evt || {}; + evt.target = evt.target || this; + // bubble + if (bubble) { + this._fireAndBubble(eventType, evt); + } else { + // no bubble + this._fire(eventType, evt); + } + return this; + }, + /** * get absolute transform of the node which takes into * account its ancestor transforms * @method * @memberof Konva.Node.prototype * @returns {Konva.Transform} */ - getAbsoluteTransform: function(top) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } - // if no argument, we can cache the result - else { - return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); - } - }, - _getAbsoluteTransform: function(top) { - var at = new Konva.Transform(), - transformsEnabled, trans; + getAbsoluteTransform: function(top) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteTransform(top); + } else { + // if no argument, we can cache the result + return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); + } + }, + _getAbsoluteTransform: function(top) { + var at = new Konva.Transform(), transformsEnabled, trans; - // start with stage and traverse downwards to self - this._eachAncestorReverse(function(node) { - transformsEnabled = node.transformsEnabled(); - trans = node.getTransform(); + // start with stage and traverse downwards to self + this._eachAncestorReverse(function(node) { + transformsEnabled = node.transformsEnabled(); + trans = node.getTransform(); - if (transformsEnabled === 'all') { - at.multiply(trans); - } - else if (transformsEnabled === 'position') { - at.translate(node.x(), node.y()); - } - }, top); - return at; - }, - /** + if (transformsEnabled === 'all') { + at.multiply(trans); + } else if (transformsEnabled === 'position') { + at.translate(node.x(), node.y()); + } + }, top); + return at; + }, + /** * get absolute scale of the node which takes into * account its ancestor scales * @method * @memberof Konva.Node.prototype * @returns {Konva.Transform} */ - getAbsoluteScale: function(top) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } - // if no argument, we can cache the result - else { - return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale); - } - }, - _getAbsoluteScale: function(top) { - // this is special logic for caching with some shapes with shadow - var parent = this; - while(parent) { - if (parent._isUnderCache) { - top = parent; - } - parent = parent.getParent(); - } + getAbsoluteScale: function(top) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteScale(top); + } else { + // if no argument, we can cache the result + return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale); + } + }, + _getAbsoluteScale: function(top) { + // this is special logic for caching with some shapes with shadow + var parent = this; + while (parent) { + if (parent._isUnderCache) { + top = parent; + } + parent = parent.getParent(); + } + var scaleX = 1, scaleY = 1; - var scaleX = 1, scaleY = 1; - - // start with stage and traverse downwards to self - this._eachAncestorReverse(function(node) { - scaleX *= node.scaleX(); - scaleY *= node.scaleY(); - }, top); - return { - x: scaleX, - y: scaleY - }; - }, - /** + // start with stage and traverse downwards to self + this._eachAncestorReverse(function(node) { + scaleX *= node.scaleX(); + scaleY *= node.scaleY(); + }, top); + return { + x: scaleX, + y: scaleY + }; + }, + /** * get transform of the node * @method * @memberof Konva.Node.prototype * @returns {Konva.Transform} */ - getTransform: function() { - return this._getCache(TRANSFORM, this._getTransform); - }, - _getTransform: function() { - var m = new Konva.Transform(), - x = this.getX(), - y = this.getY(), - rotation = Konva.getAngle(this.getRotation()), - scaleX = this.getScaleX(), - scaleY = this.getScaleY(), - skewX = this.getSkewX(), - skewY = this.getSkewY(), - offsetX = this.getOffsetX(), - offsetY = this.getOffsetY(); + getTransform: function() { + return this._getCache(TRANSFORM, this._getTransform); + }, + _getTransform: function() { + var m = new Konva.Transform(), + x = this.getX(), + y = this.getY(), + rotation = Konva.getAngle(this.getRotation()), + scaleX = this.getScaleX(), + scaleY = this.getScaleY(), + skewX = this.getSkewX(), + skewY = this.getSkewY(), + offsetX = this.getOffsetX(), + offsetY = this.getOffsetY(); - if(x !== 0 || y !== 0) { - m.translate(x, y); - } - if(rotation !== 0) { - m.rotate(rotation); - } - if(skewX !== 0 || skewY !== 0) { - m.skew(skewX, skewY); - } - if(scaleX !== 1 || scaleY !== 1) { - m.scale(scaleX, scaleY); - } - if(offsetX !== 0 || offsetY !== 0) { - m.translate(-1 * offsetX, -1 * offsetY); - } + if (x !== 0 || y !== 0) { + m.translate(x, y); + } + if (rotation !== 0) { + m.rotate(rotation); + } + if (skewX !== 0 || skewY !== 0) { + m.skew(skewX, skewY); + } + if (scaleX !== 1 || scaleY !== 1) { + m.scale(scaleX, scaleY); + } + if (offsetX !== 0 || offsetY !== 0) { + m.translate(-1 * offsetX, -1 * offsetY); + } - return m; - }, - /** + return m; + }, + /** * clone node. Returns a new Node instance with identical attributes. You can also override * the node properties with an object literal, enabling you to use an existing node as a template * for another node @@ -3794,68 +3854,76 @@ * x: 5 * }); */ - clone: function(obj) { - // instantiate new node - var attrs = Konva.Util.cloneObject(this.attrs), - key, allListeners, len, n, listener; - // filter black attrs - for (var i in CLONE_BLACK_LIST) { - var blockAttr = CLONE_BLACK_LIST[i]; - delete attrs[blockAttr]; - } - // apply attr overrides - for (key in obj) { - attrs[key] = obj[key]; - } + clone: function(obj) { + // instantiate new node + var attrs = Konva.Util.cloneObject(this.attrs), + key, + allListeners, + len, + n, + listener; + // filter black attrs + for (var i in CLONE_BLACK_LIST) { + var blockAttr = CLONE_BLACK_LIST[i]; + delete attrs[blockAttr]; + } + // apply attr overrides + for (key in obj) { + attrs[key] = obj[key]; + } - var node = new this.constructor(attrs); - // copy over listeners - for(key in this.eventListeners) { - allListeners = this.eventListeners[key]; - len = allListeners.length; - for(n = 0; n < len; n++) { - listener = allListeners[n]; - /* + var node = new this.constructor(attrs); + // copy over listeners + for (key in this.eventListeners) { + allListeners = this.eventListeners[key]; + len = allListeners.length; + for (n = 0; n < len; n++) { + listener = allListeners[n]; + /* * don't include konva namespaced listeners because * these are generated by the constructors */ - if(listener.name.indexOf(KONVA) < 0) { - // if listeners array doesn't exist, then create it - if(!node.eventListeners[key]) { - node.eventListeners[key] = []; - } - node.eventListeners[key].push(listener); - } - } + if (listener.name.indexOf(KONVA) < 0) { + // if listeners array doesn't exist, then create it + if (!node.eventListeners[key]) { + node.eventListeners[key] = []; } - return node; - }, - _toKonvaCanvas: function(config) { - config = config || {}; - - var stage = this.getStage(), - x = config.x || 0, - y = config.y || 0, - pixelRatio = config.pixelRatio || 1, - canvas = new Konva.SceneCanvas({ - width: config.width || this.getWidth() || (stage ? stage.getWidth() : 0), - height: config.height || this.getHeight() || (stage ? stage.getHeight() : 0), - pixelRatio: pixelRatio - }), - context = canvas.getContext(); - - context.save(); - - if(x || y) { - context.translate(-1 * x, -1 * y); + node.eventListeners[key].push(listener); } + } + } + return node; + }, + _toKonvaCanvas: function(config) { + config = config || {}; - this.drawScene(canvas); - context.restore(); + var stage = this.getStage(), + x = config.x || 0, + y = config.y || 0, + pixelRatio = config.pixelRatio || 1, + canvas = new Konva.SceneCanvas({ + width: config.width || + this.getWidth() || + (stage ? stage.getWidth() : 0), + height: config.height || + this.getHeight() || + (stage ? stage.getHeight() : 0), + pixelRatio: pixelRatio + }), + context = canvas.getContext(); - return canvas; - }, - /** + context.save(); + + if (x || y) { + context.translate(-1 * x, -1 * y); + } + + this.drawScene(canvas); + context.restore(); + + return canvas; + }, + /** * converts node into an canvas element. * @method * @memberof Konva.Node.prototype @@ -3869,10 +3937,10 @@ * @example * var canvas = node.toCanvas(); */ - toCanvas: function(config) { - return this._toKonvaCanvas(config)._canvas; - }, - /** + toCanvas: function(config) { + return this._toKonvaCanvas(config)._canvas; + }, + /** * Creates a composite data URL. If MIME type is not * specified, then "image/png" will result. For "image/jpeg", specify a quality * level as quality (range 0.0 - 1.0) @@ -3891,13 +3959,12 @@ * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image url. Default is 1 * @returns {String} */ - toDataURL: function(config) { - config = config || {}; - var mimeType = config.mimeType || null, - quality = config.quality || null; - return this._toKonvaCanvas(config).toDataURL(mimeType, quality); - }, - /** + toDataURL: function(config) { + config = config || {}; + var mimeType = config.mimeType || null, quality = config.quality || null; + return this._toKonvaCanvas(config).toDataURL(mimeType, quality); + }, + /** * converts node into an image. Since the toImage * method is asynchronous, a callback is required. toImage is most commonly used * to cache complex drawings as an image so that they don't have to constantly be redrawn @@ -3922,121 +3989,125 @@ * } * }); */ - toImage: function(config) { - if (!config || !config.callback) { - throw 'callback required for toImage method config argument'; - } - Konva.Util._getImage(this.toDataURL(config), function(img) { - config.callback(img); - }); - }, - setSize: function(size) { - this.setWidth(size.width); - this.setHeight(size.height); - return this; - }, - getSize: function() { - return { - width: this.getWidth(), - height: this.getHeight() - }; - }, - getWidth: function() { - return this.attrs.width || 0; - }, - getHeight: function() { - return this.attrs.height || 0; - }, - /** + toImage: function(config) { + if (!config || !config.callback) { + throw 'callback required for toImage method config argument'; + } + Konva.Util._getImage(this.toDataURL(config), function(img) { + config.callback(img); + }); + }, + setSize: function(size) { + this.setWidth(size.width); + this.setHeight(size.height); + return this; + }, + getSize: function() { + return { + width: this.getWidth(), + height: this.getHeight() + }; + }, + getWidth: function() { + return this.attrs.width || 0; + }, + getHeight: function() { + return this.attrs.height || 0; + }, + /** * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. * @method * @memberof Konva.Node.prototype * @returns {String} */ - getClassName: function() { - return this.className || this.nodeType; - }, - /** + getClassName: function() { + return this.className || this.nodeType; + }, + /** * get the node type, which may return Stage, Layer, Group, or Node * @method * @memberof Konva.Node.prototype * @returns {String} */ - getType: function() { - return this.nodeType; - }, - getDragDistance: function() { - // compare with undefined because we need to track 0 value - if (this.attrs.dragDistance !== undefined) { - return this.attrs.dragDistance; - } else if (this.parent) { - return this.parent.getDragDistance(); - } else { - return Konva.dragDistance; - } - }, - _get: function(selector) { - return this.className === selector || this.nodeType === selector ? [this] : []; - }, - _off: function(type, name) { - var evtListeners = this.eventListeners[type], - i, evtName; + getType: function() { + return this.nodeType; + }, + getDragDistance: function() { + // compare with undefined because we need to track 0 value + if (this.attrs.dragDistance !== undefined) { + return this.attrs.dragDistance; + } else if (this.parent) { + return this.parent.getDragDistance(); + } else { + return Konva.dragDistance; + } + }, + _get: function(selector) { + return this.className === selector || this.nodeType === selector + ? [this] + : []; + }, + _off: function(type, name) { + var evtListeners = this.eventListeners[type], i, evtName; - for(i = 0; i < evtListeners.length; i++) { - evtName = evtListeners[i].name; - // the following two conditions must be true in order to remove a handler: - // 1) the current event name cannot be konva unless the event name is konva - // this enables developers to force remove a konva specific listener for whatever reason - // 2) an event name is not specified, or if one is specified, it matches the current event name - if((evtName !== 'konva' || name === 'konva') && (!name || evtName === name)) { - evtListeners.splice(i, 1); - if(evtListeners.length === 0) { - delete this.eventListeners[type]; - break; - } - i--; - } - } - }, - _fireChangeEvent: function(attr, oldVal, newVal) { - this._fire(attr + CHANGE, { - oldVal: oldVal, - newVal: newVal - }); - }, - setId: function(id) { - var oldId = this.getId(); + for (i = 0; i < evtListeners.length; i++) { + evtName = evtListeners[i].name; + // the following two conditions must be true in order to remove a handler: + // 1) the current event name cannot be konva unless the event name is konva + // this enables developers to force remove a konva specific listener for whatever reason + // 2) an event name is not specified, or if one is specified, it matches the current event name + if ( + (evtName !== 'konva' || name === 'konva') && + (!name || evtName === name) + ) { + evtListeners.splice(i, 1); + if (evtListeners.length === 0) { + delete this.eventListeners[type]; + break; + } + i--; + } + } + }, + _fireChangeEvent: function(attr, oldVal, newVal) { + this._fire(attr + CHANGE, { + oldVal: oldVal, + newVal: newVal + }); + }, + setId: function(id) { + var oldId = this.getId(); - Konva._removeId(oldId); - Konva._addId(this, id); - this._setAttr(ID, id); - return this; - }, - setName: function(name) { - var oldNames = (this.getName() || '').split(/\s/g); - var newNames = (name || '').split(/\s/g); - var subname, i; - // remove all subnames - for(i = 0; i < oldNames.length; i++) { - subname = oldNames[i]; - if ((newNames.indexOf(subname)) === -1 && subname) { - Konva._removeName(subname, this._id); - } - } + Konva._removeId(oldId); + Konva._addId(this, id); + this._setAttr(ID, id); + return this; + }, + setName: function(name) { + var oldNames = (this.getName() || '').split(/\s/g); + var newNames = (name || '').split(/\s/g); + var subname, i; + // remove all subnames + for (i = 0; i < oldNames.length; i++) { + subname = oldNames[i]; + if (newNames.indexOf(subname) === -1 && subname) { + Konva._removeName(subname, this._id); + } + } - // add new names - for(i = 0; i < newNames.length; i++) { - subname = newNames[i]; - if ((oldNames.indexOf(subname) === -1) && subname) { - Konva._addName(this, subname); - } - } + // add new names + for (i = 0; i < newNames.length; i++) { + subname = newNames[i]; + if (oldNames.indexOf(subname) === -1 && subname) { + Konva._addName(this, subname); + } + } - this._setAttr(NAME, name); - return this; - }, - // naming methods - /** + this._setAttr(NAME, name); + return this; + }, + // naming methods + /** * add name to node * @method * @memberof Konva.Node.prototype @@ -4047,15 +4118,15 @@ * node.addName('selected'); * node.name(); // return 'red selected' */ - addName: function(name) { - if (!this.hasName(name)) { - var oldName = this.name(); - var newName = oldName ? (oldName + ' ' + name) : name; - this.setName(newName); - } - return this; - }, - /** + addName: function(name) { + if (!this.hasName(name)) { + var oldName = this.name(); + var newName = oldName ? oldName + ' ' + name : name; + this.setName(newName); + } + return this; + }, + /** * check is node has name * @method * @memberof Konva.Node.prototype @@ -4066,11 +4137,11 @@ * node.hasName('red'); // return true * node.hasName('selected'); // return false */ - hasName: function(name) { - var names = (this.name() || '').split(/\s/g); - return names.indexOf(name) !== -1; - }, - /** + hasName: function(name) { + var names = (this.name() || '').split(/\s/g); + return names.indexOf(name) !== -1; + }, + /** * remove name from node * @method * @memberof Konva.Node.prototype @@ -4082,16 +4153,16 @@ * node.hasName('selected'); // return false * node.name(); // return 'red' */ - removeName: function(name) { - var names = (this.name() || '').split(/\s/g); - var index = names.indexOf(name); - if (index !== -1) { - names.splice(index, 1); - this.setName(names.join(' ')); - } - return this; - }, - /** + removeName: function(name) { + var names = (this.name() || '').split(/\s/g); + var index = names.indexOf(name); + if (index !== -1) { + names.splice(index, 1); + this.setName(names.join(' ')); + } + return this; + }, + /** * set attr * @method * @memberof Konva.Node.prototype @@ -4101,104 +4172,122 @@ * @example * node.setAttr('x', 5); */ - setAttr: function(attr, val) { - var method = SET + Konva.Util._capitalize(attr), - func = this[method]; + setAttr: function(attr, val) { + var method = SET + Konva.Util._capitalize(attr), func = this[method]; - if(Konva.Util._isFunction(func)) { - func.call(this, val); - } - // otherwise set directly - else { - this._setAttr(attr, val); - } - return this; - }, - _setAttr: function(key, val) { - var oldVal; - oldVal = this.attrs[key]; - if (oldVal === val) { - return; - } - if (val === undefined || val === null) { - delete this.attrs[key]; - } else { - this.attrs[key] = val; - } - this._fireChangeEvent(key, oldVal, val); - }, - _setComponentAttr: function(key, component, val) { - var oldVal; - if(val !== undefined) { - oldVal = this.attrs[key]; + if (Konva.Util._isFunction(func)) { + func.call(this, val); + } else { + // otherwise set directly + this._setAttr(attr, val); + } + return this; + }, + _setAttr: function(key, val) { + var oldVal; + oldVal = this.attrs[key]; + if (oldVal === val) { + return; + } + if (val === undefined || val === null) { + delete this.attrs[key]; + } else { + this.attrs[key] = val; + } + this._fireChangeEvent(key, oldVal, val); + }, + _setComponentAttr: function(key, component, val) { + var oldVal; + if (val !== undefined) { + oldVal = this.attrs[key]; - if (!oldVal) { - // set value to default value using getAttr - this.attrs[key] = this.getAttr(key); - } + if (!oldVal) { + // set value to default value using getAttr + this.attrs[key] = this.getAttr(key); + } - this.attrs[key][component] = val; - this._fireChangeEvent(key, oldVal, val); - } - }, - _fireAndBubble: function(eventType, evt, compareShape) { - var okayToRun = true; + this.attrs[key][component] = val; + this._fireChangeEvent(key, oldVal, val); + } + }, + _fireAndBubble: function(eventType, evt, compareShape) { + var okayToRun = true; - if(evt && this.nodeType === SHAPE) { - evt.target = this; - } + if (evt && this.nodeType === SHAPE) { + evt.target = this; + } - if(eventType === MOUSEENTER && compareShape && (this._id === compareShape._id || (this.isAncestorOf && this.isAncestorOf(compareShape)))) { - okayToRun = false; - } - else if(eventType === MOUSELEAVE && compareShape && (this._id === compareShape._id || (this.isAncestorOf && this.isAncestorOf(compareShape)))) { - okayToRun = false; - } - if(okayToRun) { - this._fire(eventType, evt); + if ( + eventType === MOUSEENTER && + compareShape && + (this._id === compareShape._id || + (this.isAncestorOf && this.isAncestorOf(compareShape))) + ) { + okayToRun = false; + } else if ( + eventType === MOUSELEAVE && + compareShape && + (this._id === compareShape._id || + (this.isAncestorOf && this.isAncestorOf(compareShape))) + ) { + okayToRun = false; + } + if (okayToRun) { + this._fire(eventType, evt); - // simulate event bubbling - var stopBubble = - (eventType === MOUSEENTER || eventType === MOUSELEAVE) && - ((compareShape && compareShape.isAncestorOf && compareShape.isAncestorOf(this) && !compareShape.isAncestorOf(this.parent))); - if((evt && !evt.cancelBubble || !evt) && this.parent && this.parent.isListening() && (!stopBubble)) { - if (compareShape && compareShape.parent) { - this._fireAndBubble.call(this.parent, eventType, evt, compareShape.parent); - } - else { - this._fireAndBubble.call(this.parent, eventType, evt); - } - } - } - }, - _fire: function(eventType, evt) { - var events = this.eventListeners[eventType], - i; + // simulate event bubbling + var stopBubble = + (eventType === MOUSEENTER || eventType === MOUSELEAVE) && + (compareShape && + compareShape.isAncestorOf && + compareShape.isAncestorOf(this) && + !compareShape.isAncestorOf(this.parent)); + if ( + ((evt && !evt.cancelBubble) || !evt) && + this.parent && + this.parent.isListening() && + !stopBubble + ) { + if (compareShape && compareShape.parent) { + this._fireAndBubble.call( + this.parent, + eventType, + evt, + compareShape.parent + ); + } else { + this._fireAndBubble.call(this.parent, eventType, evt); + } + } + } + }, + _fire: function(eventType, evt) { + var events = this.eventListeners[eventType], i; - evt = evt || {}; - evt.currentTarget = this; - evt.type = eventType; + evt = evt || {}; + evt.currentTarget = this; + evt.type = eventType; - if (events) { - for(i = 0; i < events.length; i++) { - events[i].handler.call(this, evt); - } - } - }, - /** + if (events) { + for (i = 0; i < events.length; i++) { + events[i].handler.call(this, evt); + } + } + }, + /** * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - draw: function() { - this.drawScene(); - this.drawHit(); - return this; - } - }); + draw: function() { + this.drawScene(); + this.drawHit(); + return this; + } + }); - /** + /** * create node with JSON string or an Object. De-serializtion does not generate custom * shape drawing functions, images, or event handlers (this would make the * serialized object huge). If your app uses custom shapes, images, and @@ -4211,38 +4300,39 @@ * @param {Element} [container] optional container dom element used only if you're * creating a stage node */ - Konva.Node.create = function(data, container) { - if (Konva.Util._isString(data)) { - data = JSON.parse(data); - } - return this._createNode(data, container); - }; - Konva.Node._createNode = function(obj, container) { - var className = Konva.Node.prototype.getClassName.call(obj), - children = obj.children, - no, len, n; + Konva.Node.create = function(data, container) { + if (Konva.Util._isString(data)) { + data = JSON.parse(data); + } + return this._createNode(data, container); + }; + Konva.Node._createNode = function(obj, container) { + var className = Konva.Node.prototype.getClassName.call(obj), + children = obj.children, + no, + len, + n; - // if container was passed in, add it to attrs - if(container) { - obj.attrs.container = container; - } + // if container was passed in, add it to attrs + if (container) { + obj.attrs.container = container; + } - no = new Konva[className](obj.attrs); - if(children) { - len = children.length; - for(n = 0; n < len; n++) { - no.add(this._createNode(children[n])); - } - } + no = new Konva[className](obj.attrs); + if (children) { + len = children.length; + for (n = 0; n < len; n++) { + no.add(this._createNode(children[n])); + } + } - return no; - }; + return no; + }; + // =========================== add getters setters =========================== - // =========================== add getters setters =========================== - - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position'); - /** + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position'); + /** * get/set node position relative to parent * @name position * @method @@ -4262,9 +4352,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'x', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'x', 0); - /** + /** * get/set x position * @name x * @method @@ -4279,9 +4369,9 @@ * node.x(5); */ - Konva.Factory.addGetterSetter(Konva.Node, 'y', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'y', 0); - /** + /** * get/set y position * @name y * @method @@ -4296,9 +4386,29 @@ * node.y(5); */ - Konva.Factory.addGetterSetter(Konva.Node, 'opacity', 1); + Konva.Factory.addGetterSetter( + Konva.Node, + 'globalCompositeOperation', + 'source-over' + ); - /** + /** + * get/set globalCompositeOperation of a shape + * @name globalCompositeOperation + * @method + * @memberof Konva.Node.prototype + * @param {Number} blur + * @returns {Number} + * @example + * // get shadow blur + * var globalCompositeOperation = shape.globalCompositeOperation(); + * + * // set shadow blur + * shape.globalCompositeOperation('source-in'); + */ + Konva.Factory.addGetterSetter(Konva.Node, 'opacity', 1); + + /** * get/set opacity. Opacity values range from 0 to 1. * A node with an opacity of 0 is fully transparent, and a node * with an opacity of 1 is fully opaque @@ -4315,10 +4425,10 @@ * node.opacity(0.5); */ - Konva.Factory.addGetter(Konva.Node, 'name'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name'); + Konva.Factory.addGetter(Konva.Node, 'name'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name'); - /** + /** * get/set name * @name name * @method @@ -4336,10 +4446,10 @@ * node.name('foo bar'); */ - Konva.Factory.addGetter(Konva.Node, 'id'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id'); + Konva.Factory.addGetter(Konva.Node, 'id'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id'); - /** + /** * get/set id. Id is global for whole page. * @name id * @method @@ -4354,9 +4464,9 @@ * node.id('foo'); */ - Konva.Factory.addGetterSetter(Konva.Node, 'rotation', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'rotation', 0); - /** + /** * get/set rotation in degrees * @name rotation * @method @@ -4371,9 +4481,9 @@ * node.rotation(45); */ - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']); - /** + /** * get/set scale * @name scale * @param {Object} scale @@ -4393,9 +4503,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'scaleX', 1); + Konva.Factory.addGetterSetter(Konva.Node, 'scaleX', 1); - /** + /** * get/set scale x * @name scaleX * @param {Number} x @@ -4410,9 +4520,9 @@ * node.scaleX(2); */ - Konva.Factory.addGetterSetter(Konva.Node, 'scaleY', 1); + Konva.Factory.addGetterSetter(Konva.Node, 'scaleY', 1); - /** + /** * get/set scale y * @name scaleY * @param {Number} y @@ -4427,9 +4537,9 @@ * node.scaleY(2); */ - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']); - /** + /** * get/set skew * @name skew * @param {Object} skew @@ -4449,9 +4559,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'skewX', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'skewX', 0); - /** + /** * get/set skew x * @name skewX * @param {Number} x @@ -4466,9 +4576,9 @@ * node.skewX(3); */ - Konva.Factory.addGetterSetter(Konva.Node, 'skewY', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'skewY', 0); - /** + /** * get/set skew y * @name skewY * @param {Number} y @@ -4483,9 +4593,9 @@ * node.skewY(3); */ - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']); - /** + /** * get/set offset. Offsets the default position and rotation point * @method * @memberof Konva.Node.prototype @@ -4504,9 +4614,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'offsetX', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'offsetX', 0); - /** + /** * get/set offset x * @name offsetX * @method @@ -4521,9 +4631,9 @@ * node.offsetX(3); */ - Konva.Factory.addGetterSetter(Konva.Node, 'offsetY', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'offsetY', 0); - /** + /** * get/set offset y * @name offsetY * @method @@ -4538,10 +4648,10 @@ * node.offsetY(3); */ - Konva.Factory.addSetter(Konva.Node, 'dragDistance'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance'); + Konva.Factory.addSetter(Konva.Node, 'dragDistance'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance'); - /** + /** * get/set drag distance * @name dragDistance * @method @@ -4559,10 +4669,9 @@ * Konva.dragDistance = 3; */ - - Konva.Factory.addSetter(Konva.Node, 'width', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width'); - /** + Konva.Factory.addSetter(Konva.Node, 'width', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width'); + /** * get/set width * @name width * @method @@ -4577,9 +4686,9 @@ * node.width(100); */ - Konva.Factory.addSetter(Konva.Node, 'height', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height'); - /** + Konva.Factory.addSetter(Konva.Node, 'height', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height'); + /** * get/set height * @name height * @method @@ -4594,8 +4703,8 @@ * node.height(100); */ - Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit'); - /** + Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit'); + /** * get/set listenig attr. If you need to determine if a node is listening or not * by taking into account its parents, use the isListening() method * @name listening @@ -4617,9 +4726,7 @@ * node.listening('inherit'); */ - - - /** + /** * get/set preventDefault * By default all shapes will prevent default behaviour * of a browser on a pointer move or tap. @@ -4639,10 +4746,15 @@ * shape.preventDefault(false); */ - Konva.Factory.addGetterSetter(Konva.Node, 'preventDefault', true); + Konva.Factory.addGetterSetter(Konva.Node, 'preventDefault', true); - Konva.Factory.addGetterSetter(Konva.Node, 'filters', undefined, function(val) {this._filterUpToDate = false; return val; }); - /** + Konva.Factory.addGetterSetter(Konva.Node, 'filters', undefined, function( + val + ) { + this._filterUpToDate = false; + return val; + }); + /** * get/set filters. Filters are applied to cached canvases * @name filters * @method @@ -4666,8 +4778,8 @@ * ]); */ - Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit'); - /** + Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit'); + /** * get/set visible attr. Can be "inherit", true, or false. The default is "inherit". * If you need to determine if a node is visible or not * by taking into account its parents, use the isVisible() method @@ -4690,9 +4802,9 @@ * node.visible('inherit'); */ - Konva.Factory.addGetterSetter(Konva.Node, 'transformsEnabled', 'all'); + Konva.Factory.addGetterSetter(Konva.Node, 'transformsEnabled', 'all'); - /** + /** * get/set transforms that are enabled. Can be "all", "none", or "position". The default * is "all" * @name transformsEnabled @@ -4708,9 +4820,7 @@ * node.transformsEnabled('all'); */ - - - /** + /** * get/set node size * @name size * @method @@ -4731,15 +4841,15 @@ * height: 200 * }); */ - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size'); - Konva.Factory.backCompat(Konva.Node, { - rotateDeg: 'rotate', - setRotationDeg: 'setRotation', - getRotationDeg: 'getRotation' - }); + Konva.Factory.backCompat(Konva.Node, { + rotateDeg: 'rotate', + setRotationDeg: 'setRotation', + getRotationDeg: 'getRotation' + }); - Konva.Collection.mapMethods(Konva.Node); + Konva.Collection.mapMethods(Konva.Node); })(Konva); (function() { @@ -7264,32 +7374,32 @@ })(); (function(Konva) { - 'use strict'; - var HAS_SHADOW = 'hasShadow'; - var SHADOW_RGBA = 'shadowRGBA'; + 'use strict'; + var HAS_SHADOW = 'hasShadow'; + var SHADOW_RGBA = 'shadowRGBA'; - function _fillFunc(context) { - context.fill(); - } - function _strokeFunc(context) { - context.stroke(); - } - function _fillFuncHit(context) { - context.fill(); - } - function _strokeFuncHit(context) { - context.stroke(); - } + function _fillFunc(context) { + context.fill(); + } + function _strokeFunc(context) { + context.stroke(); + } + function _fillFuncHit(context) { + context.fill(); + } + function _strokeFuncHit(context) { + context.stroke(); + } - function _clearHasShadowCache() { - this._clearCache(HAS_SHADOW); - } + function _clearHasShadowCache() { + this._clearCache(HAS_SHADOW); + } - function _clearGetShadowRGBACache() { - this._clearCache(SHADOW_RGBA); - } + function _clearGetShadowRGBACache() { + this._clearCache(SHADOW_RGBA); + } - /** + /** * Shape constructor. Shapes are primitive objects such as rectangles, * circles, text, lines, etc. * @constructor @@ -7383,103 +7493,125 @@ * } *}); */ - Konva.Shape = function(config) { - this.__init(config); - }; + Konva.Shape = function(config) { + this.__init(config); + }; - Konva.Util.addMethods(Konva.Shape, { - __init: function(config) { - this.nodeType = 'Shape'; - this._fillFunc = _fillFunc; - this._strokeFunc = _strokeFunc; - this._fillFuncHit = _fillFuncHit; - this._strokeFuncHit = _strokeFuncHit; + Konva.Util.addMethods(Konva.Shape, { + __init: function(config) { + this.nodeType = 'Shape'; + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this._fillFuncHit = _fillFuncHit; + this._strokeFuncHit = _strokeFuncHit; - // set colorKey - var shapes = Konva.shapes; - var key; + // set colorKey + var shapes = Konva.shapes; + var key; - while(true) { - key = Konva.Util.getRandomColor(); - if(key && !( key in shapes)) { - break; - } - } + while (true) { + key = Konva.Util.getRandomColor(); + if (key && !(key in shapes)) { + break; + } + } - this.colorKey = key; - shapes[key] = this; + this.colorKey = key; + shapes[key] = this; - // call super constructor - Konva.Node.call(this, config); + // call super constructor + Konva.Node.call(this, config); - this.on('shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearHasShadowCache); + this.on( + 'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearHasShadowCache + ); - this.on('shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearGetShadowRGBACache); - }, - hasChildren: function() { - return false; - }, - getChildren: function() { - return []; - }, - /** + this.on( + 'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearGetShadowRGBACache + ); + }, + hasChildren: function() { + return false; + }, + getChildren: function() { + return []; + }, + /** * get canvas context tied to the layer * @method * @memberof Konva.Shape.prototype * @returns {Konva.Context} */ - getContext: function() { - return this.getLayer().getContext(); - }, - /** + getContext: function() { + return this.getLayer().getContext(); + }, + /** * get canvas renderer tied to the layer. Note that this returns a canvas renderer, not a canvas element * @method * @memberof Konva.Shape.prototype * @returns {Konva.Canvas} */ - getCanvas: function() { - return this.getLayer().getCanvas(); - }, - /** + getCanvas: function() { + return this.getLayer().getCanvas(); + }, + /** * returns whether or not a shadow will be rendered * @method * @memberof Konva.Shape.prototype * @returns {Boolean} */ - hasShadow: function() { - return this._getCache(HAS_SHADOW, this._hasShadow); - }, - _hasShadow: function() { - return this.getShadowEnabled() && (this.getShadowOpacity() !== 0 && !!(this.getShadowColor() || this.getShadowBlur() || this.getShadowOffsetX() || this.getShadowOffsetY())); - }, - getShadowRGBA: function() { - return this._getCache(SHADOW_RGBA, this._getShadowRGBA); - }, - _getShadowRGBA: function() { - if (this.hasShadow()) { - var rgba = Konva.Util.colorToRGBA(this.shadowColor()); - return 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + (rgba.a * (this.getShadowOpacity() || 1)) + ')'; - } - }, - /** + hasShadow: function() { + return this._getCache(HAS_SHADOW, this._hasShadow); + }, + _hasShadow: function() { + return this.getShadowEnabled() && + (this.getShadowOpacity() !== 0 && + !!(this.getShadowColor() || + this.getShadowBlur() || + this.getShadowOffsetX() || + this.getShadowOffsetY())); + }, + getShadowRGBA: function() { + return this._getCache(SHADOW_RGBA, this._getShadowRGBA); + }, + _getShadowRGBA: function() { + if (this.hasShadow()) { + var rgba = Konva.Util.colorToRGBA(this.shadowColor()); + return 'rgba(' + + rgba.r + + ',' + + rgba.g + + ',' + + rgba.b + + ',' + + rgba.a * (this.getShadowOpacity() || 1) + + ')'; + } + }, + /** * returns whether or not the shape will be filled * @method * @memberof Konva.Shape.prototype * @returns {Boolean} */ - hasFill: function() { - return !!(this.getFill() || this.getFillPatternImage() || this.getFillLinearGradientColorStops() || this.getFillRadialGradientColorStops()); - }, - /** + hasFill: function() { + return !!(this.getFill() || + this.getFillPatternImage() || + this.getFillLinearGradientColorStops() || + this.getFillRadialGradientColorStops()); + }, + /** * returns whether or not the shape will be stroked * @method * @memberof Konva.Shape.prototype * @returns {Boolean} */ - hasStroke: function() { - return this.strokeEnabled() && !!(this.stroke()); - }, - /** + hasStroke: function() { + return this.strokeEnabled() && !!this.stroke(); + }, + /** * determines if point is in the shape, regardless if other shapes are on top of it. Note: because * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times * consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible @@ -7491,27 +7623,40 @@ * @param {Number} point.y * @returns {Boolean} */ - intersects: function(point) { - var stage = this.getStage(), - bufferHitCanvas = stage.bufferHitCanvas, - p; + intersects: function(point) { + var stage = this.getStage(), bufferHitCanvas = stage.bufferHitCanvas, p; - bufferHitCanvas.getContext().clear(); - this.drawScene(bufferHitCanvas); - p = bufferHitCanvas.context.getImageData(Math.round(point.x), Math.round(point.y), 1, 1).data; - return p[3] > 0; - }, - // extends Node.prototype.destroy - destroy: function() { - Konva.Node.prototype.destroy.call(this); - delete Konva.shapes[this.colorKey]; - return this; - }, - _useBufferCanvas: function(caching) { - return !caching && (this.perfectDrawEnabled() && (this.getAbsoluteOpacity() !== 1) && this.hasFill() && this.hasStroke() && this.getStage()) || - (this.perfectDrawEnabled() && this.hasShadow() && (this.getAbsoluteOpacity() !== 1) && this.hasFill() && this.hasStroke() && this.getStage()); - }, - /** + bufferHitCanvas.getContext().clear(); + this.drawHit(bufferHitCanvas); + p = bufferHitCanvas.context.getImageData( + Math.round(point.x), + Math.round(point.y), + 1, + 1 + ).data; + return p[3] > 0; + }, + // extends Node.prototype.destroy + destroy: function() { + Konva.Node.prototype.destroy.call(this); + delete Konva.shapes[this.colorKey]; + return this; + }, + _useBufferCanvas: function(caching) { + return (!caching && + (this.perfectDrawEnabled() && + this.getAbsoluteOpacity() !== 1 && + this.hasFill() && + this.hasStroke() && + this.getStage())) || + (this.perfectDrawEnabled() && + this.hasShadow() && + this.getAbsoluteOpacity() !== 1 && + this.hasFill() && + this.hasStroke() && + this.getStage()); + }, + /** * return self rectangle (x, y, width, height) of shape. * This method are not taken into account transformation and styles. * @method @@ -7523,193 +7668,217 @@ * circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()} * */ - getSelfRect: function() { - var size = this.getSize(); - return { - x: this._centroid ? Math.round(-size.width / 2) : 0, - y: this._centroid ? Math.round(-size.height / 2) : 0, - width: size.width, - height: size.height - }; - }, - getClientRect: function(skipTransform) { - var fillRect = this.getSelfRect(); + getSelfRect: function() { + var size = this.getSize(); + return { + x: this._centroid ? Math.round((-size.width) / 2) : 0, + y: this._centroid ? Math.round((-size.height) / 2) : 0, + width: size.width, + height: size.height + }; + }, + getClientRect: function(skipTransform) { + var fillRect = this.getSelfRect(); - var strokeWidth = (this.hasStroke() && this.strokeWidth()) || 0; - var fillAndStrokeWidth = fillRect.width + strokeWidth; - var fillAndStrokeHeight = fillRect.height + strokeWidth; + var strokeWidth = (this.hasStroke() && this.strokeWidth()) || 0; + var fillAndStrokeWidth = fillRect.width + strokeWidth; + var fillAndStrokeHeight = fillRect.height + strokeWidth; - var shadowOffsetX = this.hasShadow() ? this.shadowOffsetX() : 0; - var shadowOffsetY = this.hasShadow() ? this.shadowOffsetY() : 0; + var shadowOffsetX = this.hasShadow() ? this.shadowOffsetX() : 0; + var shadowOffsetY = this.hasShadow() ? this.shadowOffsetY() : 0; - var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); - var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); + var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); + var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); - var blurRadius = (this.hasShadow() && this.shadowBlur() || 0); + var blurRadius = (this.hasShadow() && this.shadowBlur()) || 0; - var width = preWidth + blurRadius * 2; - var height = preHeight + blurRadius * 2; + var width = preWidth + blurRadius * 2; + var height = preHeight + blurRadius * 2; - // if stroke, for example = 3 - // we need to set x to 1.5, but after Math.round it will be 2 - // as we have additional offset we need to increase width and height by 1 pixel - var roundingOffset = 0; - if (Math.round(strokeWidth / 2) !== strokeWidth / 2) { - roundingOffset = 1; - } - var rect = { - width: width + roundingOffset, - height: height + roundingOffset, - x: -Math.round(strokeWidth / 2 + blurRadius) + Math.min(shadowOffsetX, 0) + fillRect.x, - y: -Math.round(strokeWidth / 2 + blurRadius) + Math.min(shadowOffsetY, 0) + fillRect.y - }; - if (!skipTransform) { - return this._transformedRect(rect); - } - return rect; - }, - drawScene: function(can, top, caching, skipBuffer) { - var layer = this.getLayer(), - canvas = can || layer.getCanvas(), - context = canvas.getContext(), - cachedCanvas = this._cache.canvas, - drawFunc = this.sceneFunc(), - hasShadow = this.hasShadow(), - hasStroke = this.hasStroke(), - stage, bufferCanvas, bufferContext; + // if stroke, for example = 3 + // we need to set x to 1.5, but after Math.round it will be 2 + // as we have additional offset we need to increase width and height by 1 pixel + var roundingOffset = 0; + if (Math.round(strokeWidth / 2) !== strokeWidth / 2) { + roundingOffset = 1; + } + var rect = { + width: width + roundingOffset, + height: height + roundingOffset, + x: -Math.round(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetX, 0) + + fillRect.x, + y: -Math.round(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetY, 0) + + fillRect.y + }; + if (!skipTransform) { + return this._transformedRect(rect); + } + return rect; + }, + drawScene: function(can, top, caching, skipBuffer) { + var layer = this.getLayer(), + canvas = can || layer.getCanvas(), + context = canvas.getContext(), + cachedCanvas = this._cache.canvas, + drawFunc = this.sceneFunc(), + hasShadow = this.hasShadow(), + hasStroke = this.hasStroke(), + stage, + bufferCanvas, + bufferContext; - if(!this.isVisible()) { - return this; - } - if (cachedCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedSceneCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - // if buffer canvas is needed - if (this._useBufferCanvas(caching) && !skipBuffer) { - stage = this.getStage(); - bufferCanvas = stage.bufferCanvas; - bufferContext = bufferCanvas.getContext(); - bufferContext.clear(); - bufferContext.save(); - bufferContext._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - if (!caching) { - if (layer) { - layer._applyTransform(this, bufferContext, top); - } else { - var m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - } + if (!this.isVisible()) { + return this; + } + if (cachedCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedSceneCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + // if buffer canvas is needed + if (this._useBufferCanvas(caching) && !skipBuffer) { + stage = this.getStage(); + bufferCanvas = stage.bufferCanvas; + bufferContext = bufferCanvas.getContext(); + bufferContext.clear(); + bufferContext.save(); + bufferContext._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + if (!caching) { + if (layer) { + layer._applyTransform(this, bufferContext, top); + } else { + var m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + } - drawFunc.call(this, bufferContext); - bufferContext.restore(); + drawFunc.call(this, bufferContext); + bufferContext.restore(); - var ratio = bufferCanvas.pixelRatio; - if (hasShadow && !canvas.hitCanvas) { - context.save(); - context._applyShadow(this); - context._applyOpacity(this); - context.drawImage(bufferCanvas._canvas, 0, 0, bufferCanvas.width / ratio, bufferCanvas.height / ratio); - context.restore(); - } else { - context._applyOpacity(this); - context.drawImage(bufferCanvas._canvas, 0, 0, bufferCanvas.width / ratio, bufferCanvas.height / ratio); - } - } - // if buffer canvas is not needed - else { - context._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - if (!caching) { - if (layer) { - layer._applyTransform(this, context, top); - } else { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - } + var ratio = bufferCanvas.pixelRatio; + if (hasShadow && !canvas.hitCanvas) { + context.save(); - if (hasShadow && hasStroke && !canvas.hitCanvas) { - context.save(); - // apply shadow - if (!caching) { - context._applyOpacity(this); - } - context._applyShadow(this); - drawFunc.call(this, context); - context.restore(); - // if shape has stroke we need to redraw shape - // otherwise we will see a shadow under stroke (and over fill) - // but I think this is unexpected behavior - if (this.hasFill() && this.getShadowForStrokeEnabled()) { - drawFunc.call(this, context); - } - } else if (hasShadow && !canvas.hitCanvas) { - context.save(); - if (!caching) { - context._applyOpacity(this); - } - context._applyShadow(this); - drawFunc.call(this, context); - context.restore(); - } else { - if (!caching) { - context._applyOpacity(this); - } - drawFunc.call(this, context); - } - } - context.restore(); - return this; - }, - drawHit: function(can, top, caching) { - var layer = this.getLayer(), - canvas = can || layer.hitCanvas, - context = canvas.getContext(), - drawFunc = this.hitFunc() || this.sceneFunc(), - cachedCanvas = this._cache.canvas, - cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + context._applyShadow(this); + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.drawImage( + bufferCanvas._canvas, + 0, + 0, + bufferCanvas.width / ratio, + bufferCanvas.height / ratio + ); + context.restore(); + } else { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.drawImage( + bufferCanvas._canvas, + 0, + 0, + bufferCanvas.width / ratio, + bufferCanvas.height / ratio + ); + } + } else { + // if buffer canvas is not needed + context._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + if (!caching) { + if (layer) { + layer._applyTransform(this, context, top); + } else { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + } - if(!this.shouldDrawHit(canvas)) { - return this; - } - if (layer) { - layer.clearHitCache(); - } - if (cachedHitCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedHitCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - context._applyLineJoin(this); - if (!caching) { - if (layer) { - layer._applyTransform(this, context, top); - } else { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - } + if (hasShadow && hasStroke && !canvas.hitCanvas) { + context.save(); + // apply shadow + if (!caching) { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + context._applyShadow(this); + + drawFunc.call(this, context); + context.restore(); + // if shape has stroke we need to redraw shape + // otherwise we will see a shadow under stroke (and over fill) + // but I think this is unexpected behavior + if (this.hasFill() && this.getShadowForStrokeEnabled()) { drawFunc.call(this, context); - context.restore(); - return this; - }, - /** + } + } else if (hasShadow && !canvas.hitCanvas) { + context.save(); + if (!caching) { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + context._applyShadow(this); + drawFunc.call(this, context); + context.restore(); + } else { + if (!caching) { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + drawFunc.call(this, context); + } + } + context.restore(); + return this; + }, + drawHit: function(can, top, caching) { + var layer = this.getLayer(), + canvas = can || layer.hitCanvas, + context = canvas.getContext(), + drawFunc = this.hitFunc() || this.sceneFunc(), + cachedCanvas = this._cache.canvas, + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (!this.shouldDrawHit(canvas)) { + return this; + } + if (layer) { + layer.clearHitCache(); + } + if (cachedHitCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedHitCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + context._applyLineJoin(this); + if (!caching) { + if (layer) { + layer._applyTransform(this, context, top); + } else { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + } + drawFunc.call(this, context); + context.restore(); + return this; + }, + /** * draw hit graph using the cached scene canvas * @method * @memberof Konva.Shape.prototype @@ -7721,53 +7890,58 @@ * shape.cache(); * shape.drawHitFromCache(); */ - drawHitFromCache: function(alphaThreshold) { - var threshold = alphaThreshold || 0, - cachedCanvas = this._cache.canvas, - sceneCanvas = this._getCachedSceneCanvas(), - hitCanvas = cachedCanvas.hit, - hitContext = hitCanvas.getContext(), - hitWidth = hitCanvas.getWidth(), - hitHeight = hitCanvas.getHeight(), - hitImageData, hitData, len, rgbColorKey, i, alpha; + drawHitFromCache: function(alphaThreshold) { + var threshold = alphaThreshold || 0, + cachedCanvas = this._cache.canvas, + sceneCanvas = this._getCachedSceneCanvas(), + hitCanvas = cachedCanvas.hit, + hitContext = hitCanvas.getContext(), + hitWidth = hitCanvas.getWidth(), + hitHeight = hitCanvas.getHeight(), + hitImageData, + hitData, + len, + rgbColorKey, + i, + alpha; - hitContext.clear(); - hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); + hitContext.clear(); + hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); - try { - hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); - hitData = hitImageData.data; - len = hitData.length; - rgbColorKey = Konva.Util._hexToRgb(this.colorKey); + try { + hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); + hitData = hitImageData.data; + len = hitData.length; + rgbColorKey = Konva.Util._hexToRgb(this.colorKey); - // replace non transparent pixels with color key - for(i = 0; i < len; i += 4) { - alpha = hitData[i + 3]; - if (alpha > threshold) { - hitData[i] = rgbColorKey.r; - hitData[i + 1] = rgbColorKey.g; - hitData[i + 2] = rgbColorKey.b; - hitData[i + 3] = 255; - } - else { - hitData[i + 3] = 0; - } - } - hitContext.putImageData(hitImageData, 0, 0); - } - catch(e) { - Konva.Util.error('Unable to draw hit graph from cached scene canvas. ' + e.message); - } - - return this; + // replace non transparent pixels with color key + for (i = 0; i < len; i += 4) { + alpha = hitData[i + 3]; + if (alpha > threshold) { + hitData[i] = rgbColorKey.r; + hitData[i + 1] = rgbColorKey.g; + hitData[i + 2] = rgbColorKey.b; + hitData[i + 3] = 255; + } else { + hitData[i + 3] = 0; + } } - }); - Konva.Util.extend(Konva.Shape, Konva.Node); + hitContext.putImageData(hitImageData, 0, 0); + } catch (e) { + Konva.Util.error( + 'Unable to draw hit graph from cached scene canvas. ' + e.message + ); + } - // add getters and setters - Konva.Factory.addGetterSetter(Konva.Shape, 'stroke'); + return this; + } + }); + Konva.Util.extend(Konva.Shape, Konva.Node); - /** + // add getters and setters + Konva.Factory.addGetterSetter(Konva.Shape, 'stroke'); + + /** * get/set stroke color * @name stroke * @method @@ -7791,15 +7965,34 @@ * shape.stroke('rgba(0,255,0,0.5'); */ - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeRed', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeGreen', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeBlue', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeAlpha', 1, Konva.Validators.alphaComponent); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeAlpha', + 1, + Konva.Validators.alphaComponent + ); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeWidth', 2); - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeWidth', 2); - - /** + /** * get/set stroke width * @name strokeWidth * @method @@ -7814,9 +8007,9 @@ * shape.strokeWidth(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeHitEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeHitEnabled', true); - /** + /** * get/set strokeHitEnabled property. Useful for performance optimization. * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false` @@ -7835,9 +8028,9 @@ * shape.strokeHitEnabled(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'perfectDrawEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'perfectDrawEnabled', true); - /** + /** * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance. * See http://konvajs.github.io/docs/performance/Disable_Perfect_Draw.html for more information. * Default value is true @@ -7854,9 +8047,9 @@ * shape.perfectDrawEnabled(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowForStrokeEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowForStrokeEnabled', true); - /** + /** * get/set shadowForStrokeEnabled. Useful for performance optimization. * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will be no draw shadow for stroke. * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line with have no shadow!. @@ -7874,9 +8067,9 @@ * shape.shadowForStrokeEnabled(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'lineJoin'); + Konva.Factory.addGetterSetter(Konva.Shape, 'lineJoin'); - /** + /** * get/set line join. Can be miter, round, or bevel. The * default is miter * @name lineJoin @@ -7892,9 +8085,9 @@ * shape.lineJoin('round'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'lineCap'); + Konva.Factory.addGetterSetter(Konva.Shape, 'lineCap'); - /** + /** * get/set line cap. Can be butt, round, or square * @name lineCap * @method @@ -7909,9 +8102,9 @@ * shape.lineCap('round'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'sceneFunc'); + Konva.Factory.addGetterSetter(Konva.Shape, 'sceneFunc'); - /** + /** * get/set scene draw function * @name sceneFunc * @method @@ -7931,9 +8124,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'hitFunc'); + Konva.Factory.addGetterSetter(Konva.Shape, 'hitFunc'); - /** + /** * get/set hit draw function * @name hitFunc * @method @@ -7953,9 +8146,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'dash'); + Konva.Factory.addGetterSetter(Konva.Shape, 'dash'); - /** + /** * get/set dash array for stroke. * @name dash * @method @@ -7971,10 +8164,24 @@ * line.dash([10, 20, 0.001, 20]); */ + Konva.Factory.addGetterSetter(Konva.Shape, 'dashOffset', 0); - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowColor'); + /** + * get/set dash offset for stroke. + * @name dash + * @method + * @memberof Konva.Shape.prototype + * @param {Number} dash offset + * @returns {Number} + * @example + * // apply dashed stroke that is 10px long and 5 pixels apart with an offset of 5px + * line.dash([10, 5]); + * line.dashOffset(5); + */ - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowColor'); + + /** * get/set shadow color * @name shadowColor * @method @@ -7998,14 +8205,34 @@ * shape.shadowColor('rgba(0,255,0,0.5'); */ - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowRed', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowGreen', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowBlue', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowAlpha', 1, Konva.Validators.alphaComponent); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowAlpha', + 1, + Konva.Validators.alphaComponent + ); - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowBlur'); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowBlur'); - /** + /** * get/set shadow blur * @name shadowBlur * @method @@ -8020,9 +8247,9 @@ * shape.shadowBlur(10); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOpacity'); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOpacity'); - /** + /** * get/set shadow opacity. must be a value between 0 and 1 * @name shadowOpacity * @method @@ -8037,9 +8264,12 @@ * shape.shadowOpacity(0.5); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'shadowOffset', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'shadowOffset', [ + 'x', + 'y' + ]); - /** + /** * get/set shadow offset * @name shadowOffset * @method @@ -8059,9 +8289,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetX', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetX', 0); - /** + /** * get/set shadow offset x * @name shadowOffsetX * @method @@ -8076,9 +8306,9 @@ * shape.shadowOffsetX(5); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetY', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetY', 0); - /** + /** * get/set shadow offset y * @name shadowOffsetY * @method @@ -8093,9 +8323,9 @@ * shape.shadowOffsetY(5); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternImage'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternImage'); - /** + /** * get/set fill pattern image * @name fillPatternImage * @method @@ -8114,9 +8344,9 @@ * imageObj.src = 'path/to/image/jpg'; */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fill'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fill'); - /** + /** * get/set fill color * @name fill * @method @@ -8143,14 +8373,34 @@ * shape.fill(null); */ - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillRed', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillGreen', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillBlue', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillAlpha', 1, Konva.Validators.alphaComponent); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillAlpha', + 1, + Konva.Validators.alphaComponent + ); - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternX', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternX', 0); - /** + /** * get/set fill pattern x * @name fillPatternX * @method @@ -8164,9 +8414,9 @@ * shape.fillPatternX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternY', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternY', 0); - /** + /** * get/set fill pattern y * @name fillPatternY * @method @@ -8180,9 +8430,9 @@ * shape.fillPatternY(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientColorStops'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientColorStops'); - /** + /** * get/set fill linear gradient color stops * @name fillLinearGradientColorStops * @method @@ -8198,9 +8448,13 @@ * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientStartRadius', 0); + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartRadius', + 0 + ); - /** + /** * get/set fill radial gradient start radius * @name fillRadialGradientStartRadius * @method @@ -8215,9 +8469,9 @@ * shape.fillRadialGradientStartRadius(0); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndRadius', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndRadius', 0); - /** + /** * get/set fill radial gradient end radius * @name fillRadialGradientEndRadius * @method @@ -8232,9 +8486,9 @@ * shape.fillRadialGradientEndRadius(100); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientColorStops'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientColorStops'); - /** + /** * get/set fill radial gradient color stops * @name fillRadialGradientColorStops * @method @@ -8250,9 +8504,9 @@ * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRepeat', 'repeat'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRepeat', 'repeat'); - /** + /** * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' * @name fillPatternRepeat * @method @@ -8270,9 +8524,9 @@ * shape.fillPatternRepeat('no repeat'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillEnabled', true); - /** + /** * get/set fill enabled flag * @name fillEnabled * @method @@ -8290,9 +8544,9 @@ * shape.fillEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeEnabled', true); - /** + /** * get/set stroke enabled flag * @name strokeEnabled * @method @@ -8310,9 +8564,9 @@ * shape.strokeEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowEnabled', true); - /** + /** * get/set shadow enabled flag * @name shadowEnabled * @method @@ -8330,9 +8584,9 @@ * shape.shadowEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'dashEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'dashEnabled', true); - /** + /** * get/set dash enabled flag * @name dashEnabled * @method @@ -8350,9 +8604,9 @@ * shape.dashEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeScaleEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeScaleEnabled', true); - /** + /** * get/set strokeScale enabled flag * @name strokeScaleEnabled * @method @@ -8370,9 +8624,9 @@ * shape.strokeScaleEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPriority', 'color'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPriority', 'color'); - /** + /** * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. * This is handy if you want to toggle between different fill types. * @name fillPriority @@ -8388,9 +8642,12 @@ * shape.fillPriority('linear-gradient'); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternOffset', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternOffset', [ + 'x', + 'y' + ]); - /** + /** * get/set fill pattern offset * @name fillPatternOffset * @method @@ -8410,9 +8667,8 @@ * }); */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetX', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetX', 0); + /** * get/set fill pattern offset x * @name fillPatternOffsetX * @method @@ -8427,8 +8683,8 @@ * shape.fillPatternOffsetX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetY', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetY', 0); + /** * get/set fill pattern offset y * @name fillPatternOffsetY * @method @@ -8443,9 +8699,12 @@ * shape.fillPatternOffsetY(10); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternScale', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternScale', [ + 'x', + 'y' + ]); - /** + /** * get/set fill pattern scale * @name fillPatternScale * @method @@ -8465,9 +8724,8 @@ * }); */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleX', 1); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleX', 1); + /** * get/set fill pattern scale x * @name fillPatternScaleX * @method @@ -8482,8 +8740,8 @@ * shape.fillPatternScaleX(2); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleY', 1); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleY', 1); + /** * get/set fill pattern scale y * @name fillPatternScaleY * @method @@ -8498,9 +8756,13 @@ * shape.fillPatternScaleY(2); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillLinearGradientStartPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPoint', + ['x', 'y'] + ); - /** + /** * get/set fill linear gradient start point * @name fillLinearGradientStartPoint * @method @@ -8520,8 +8782,12 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientStartPointX', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPointX', + 0 + ); + /** * get/set fill linear gradient start point x * @name fillLinearGradientStartPointX * @method @@ -8536,8 +8802,12 @@ * shape.fillLinearGradientStartPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientStartPointY', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPointY', + 0 + ); + /** * get/set fill linear gradient start point y * @name fillLinearGradientStartPointY * @method @@ -8552,9 +8822,13 @@ * shape.fillLinearGradientStartPointY(20); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillLinearGradientEndPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillLinearGradientEndPoint', + ['x', 'y'] + ); - /** + /** * get/set fill linear gradient end point * @name fillLinearGradientEndPoint * @method @@ -8574,8 +8848,8 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointX', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointX', 0); + /** * get/set fill linear gradient end point x * @name fillLinearGradientEndPointX * @method @@ -8590,8 +8864,8 @@ * shape.fillLinearGradientEndPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointY', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointY', 0); + /** * get/set fill linear gradient end point y * @name fillLinearGradientEndPointY * @method @@ -8606,9 +8880,13 @@ * shape.fillLinearGradientEndPointY(20); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillRadialGradientStartPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPoint', + ['x', 'y'] + ); - /** + /** * get/set fill radial gradient start point * @name fillRadialGradientStartPoint * @method @@ -8628,8 +8906,12 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientStartPointX', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPointX', + 0 + ); + /** * get/set fill radial gradient start point x * @name fillRadialGradientStartPointX * @method @@ -8644,8 +8926,12 @@ * shape.fillRadialGradientStartPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientStartPointY', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPointY', + 0 + ); + /** * get/set fill radial gradient start point y * @name fillRadialGradientStartPointY * @method @@ -8660,9 +8946,13 @@ * shape.fillRadialGradientStartPointY(20); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillRadialGradientEndPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillRadialGradientEndPoint', + ['x', 'y'] + ); - /** + /** * get/set fill radial gradient end point * @name fillRadialGradientEndPoint * @method @@ -8682,8 +8972,8 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointX', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointX', 0); + /** * get/set fill radial gradient end point x * @name fillRadialGradientEndPointX * @method @@ -8698,8 +8988,8 @@ * shape.fillRadialGradientEndPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointY', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointY', 0); + /** * get/set fill radial gradient end point y * @name fillRadialGradientEndPointY * @method @@ -8714,9 +9004,9 @@ * shape.fillRadialGradientEndPointY(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRotation', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRotation', 0); - /** + /** * get/set fill pattern rotation in degrees * @name fillPatternRotation * @method @@ -8731,84 +9021,96 @@ * shape.fillPatternRotation(20); */ + Konva.Factory.backCompat(Konva.Shape, { + dashArray: 'dash', + getDashArray: 'getDash', + setDashArray: 'getDash', - Konva.Factory.backCompat(Konva.Shape, { - dashArray: 'dash', - getDashArray: 'getDash', - setDashArray: 'getDash', + drawFunc: 'sceneFunc', + getDrawFunc: 'getSceneFunc', + setDrawFunc: 'setSceneFunc', - drawFunc: 'sceneFunc', - getDrawFunc: 'getSceneFunc', - setDrawFunc: 'setSceneFunc', + drawHitFunc: 'hitFunc', + getDrawHitFunc: 'getHitFunc', + setDrawHitFunc: 'setHitFunc' + }); - drawHitFunc: 'hitFunc', - getDrawHitFunc: 'getHitFunc', - setDrawHitFunc: 'setHitFunc' - }); - - Konva.Collection.mapMethods(Konva.Shape); + Konva.Collection.mapMethods(Konva.Shape); })(Konva); (function() { - 'use strict'; - // CONSTANTS - var STAGE = 'Stage', - STRING = 'string', - PX = 'px', + 'use strict'; + // CONSTANTS + var STAGE = 'Stage', + STRING = 'string', + PX = 'px', + MOUSEOUT = 'mouseout', + MOUSELEAVE = 'mouseleave', + MOUSEOVER = 'mouseover', + MOUSEENTER = 'mouseenter', + MOUSEMOVE = 'mousemove', + MOUSEDOWN = 'mousedown', + MOUSEUP = 'mouseup', + CONTEXTMENU = 'contextmenu', + CLICK = 'click', + DBL_CLICK = 'dblclick', + TOUCHSTART = 'touchstart', + TOUCHEND = 'touchend', + TAP = 'tap', + DBL_TAP = 'dbltap', + TOUCHMOVE = 'touchmove', + DOMMOUSESCROLL = 'DOMMouseScroll', + MOUSEWHEEL = 'mousewheel', + WHEEL = 'wheel', + CONTENT_MOUSEOUT = 'contentMouseout', + CONTENT_MOUSEOVER = 'contentMouseover', + CONTENT_MOUSEMOVE = 'contentMousemove', + CONTENT_MOUSEDOWN = 'contentMousedown', + CONTENT_MOUSEUP = 'contentMouseup', + CONTENT_CONTEXTMENU = 'contentContextmenu', + CONTENT_CLICK = 'contentClick', + CONTENT_DBL_CLICK = 'contentDblclick', + CONTENT_TOUCHSTART = 'contentTouchstart', + CONTENT_TOUCHEND = 'contentTouchend', + CONTENT_DBL_TAP = 'contentDbltap', + CONTENT_TAP = 'contentTap', + CONTENT_TOUCHMOVE = 'contentTouchmove', + CONTENT_WHEEL = 'contentWheel', + DIV = 'div', + RELATIVE = 'relative', + KONVA_CONTENT = 'konvajs-content', + SPACE = ' ', + UNDERSCORE = '_', + CONTAINER = 'container', + EMPTY_STRING = '', + EVENTS = [ + MOUSEDOWN, + MOUSEMOVE, + MOUSEUP, + MOUSEOUT, + TOUCHSTART, + TOUCHMOVE, + TOUCHEND, + MOUSEOVER, + DOMMOUSESCROLL, + MOUSEWHEEL, + WHEEL, + CONTEXTMENU + ], + // cached variables + eventsLength = EVENTS.length; - MOUSEOUT = 'mouseout', - MOUSELEAVE = 'mouseleave', - MOUSEOVER = 'mouseover', - MOUSEENTER = 'mouseenter', - MOUSEMOVE = 'mousemove', - MOUSEDOWN = 'mousedown', - MOUSEUP = 'mouseup', - CONTEXTMENU = 'contextmenu', - CLICK = 'click', - DBL_CLICK = 'dblclick', - TOUCHSTART = 'touchstart', - TOUCHEND = 'touchend', - TAP = 'tap', - DBL_TAP = 'dbltap', - TOUCHMOVE = 'touchmove', - DOMMOUSESCROLL = 'DOMMouseScroll', - MOUSEWHEEL = 'mousewheel', - WHEEL = 'wheel', + function addEvent(ctx, eventName) { + ctx.content.addEventListener( + eventName, + function(evt) { + ctx[UNDERSCORE + eventName](evt); + }, + false + ); + } - CONTENT_MOUSEOUT = 'contentMouseout', - CONTENT_MOUSEOVER = 'contentMouseover', - CONTENT_MOUSEMOVE = 'contentMousemove', - CONTENT_MOUSEDOWN = 'contentMousedown', - CONTENT_MOUSEUP = 'contentMouseup', - CONTENT_CONTEXTMENU = 'contentContextmenu', - CONTENT_CLICK = 'contentClick', - CONTENT_DBL_CLICK = 'contentDblclick', - CONTENT_TOUCHSTART = 'contentTouchstart', - CONTENT_TOUCHEND = 'contentTouchend', - CONTENT_DBL_TAP = 'contentDbltap', - CONTENT_TAP = 'contentTap', - CONTENT_TOUCHMOVE = 'contentTouchmove', - CONTENT_WHEEL = 'contentWheel', - - DIV = 'div', - RELATIVE = 'relative', - KONVA_CONTENT = 'konvajs-content', - SPACE = ' ', - UNDERSCORE = '_', - CONTAINER = 'container', - EMPTY_STRING = '', - EVENTS = [MOUSEDOWN, MOUSEMOVE, MOUSEUP, MOUSEOUT, TOUCHSTART, TOUCHMOVE, TOUCHEND, MOUSEOVER, DOMMOUSESCROLL, MOUSEWHEEL, WHEEL, CONTEXTMENU], - - // cached variables - eventsLength = EVENTS.length; - - function addEvent(ctx, eventName) { - ctx.content.addEventListener(eventName, function(evt) { - ctx[UNDERSCORE + eventName](evt); - }, false); - } - - /** + /** * Stage constructor. A stage is used to contain multiple layers * @constructor * @memberof Konva @@ -8842,158 +9144,156 @@ * container: 'containerId' // or "#containerId" or ".containerClass" * }); */ - Konva.Stage = function(config) { - this.___init(config); - }; + Konva.Stage = function(config) { + this.___init(config); + }; - Konva.Util.addMethods(Konva.Stage, { - ___init: function(config) { - this.nodeType = STAGE; - // call super constructor - Konva.Container.call(this, config); - this._id = Konva.idCounter++; - this._buildDOM(); - this._bindContentEvents(); - this._enableNestedTransforms = false; - Konva.stages.push(this); - }, - _validateAdd: function(child) { - if (child.getType() !== 'Layer') { - Konva.Util.throw('You may only add layers to the stage.'); - } - }, - /** + Konva.Util.addMethods(Konva.Stage, { + ___init: function(config) { + this.nodeType = STAGE; + // call super constructor + Konva.Container.call(this, config); + this._id = Konva.idCounter++; + this._buildDOM(); + this._bindContentEvents(); + this._enableNestedTransforms = false; + Konva.stages.push(this); + }, + _validateAdd: function(child) { + if (child.getType() !== 'Layer') { + Konva.Util.throw('You may only add layers to the stage.'); + } + }, + /** * set container dom element which contains the stage wrapper div element * @method * @memberof Konva.Stage.prototype * @param {DomElement} container can pass in a dom element or id string */ - setContainer: function (container) { - if (typeof container === STRING) { - if (container.charAt(0) === '.') { - var className = container.slice(1); - container = Konva.document.getElementsByClassName(className)[0]; - } else { - var id; - if (container.charAt(0) !== '#') { - id = container; - } else { - id = container.slice(1); - } - container = Konva.document.getElementById(id); - } - if (!container) { - throw 'Can not find container in document with id ' + id; - } - } - this._setAttr(CONTAINER, container); - return this; - }, - shouldDrawHit: function() { - return true; - }, - draw: function() { - Konva.Node.prototype.draw.call(this); - return this; - }, - /** + setContainer: function(container) { + if (typeof container === STRING) { + if (container.charAt(0) === '.') { + var className = container.slice(1); + container = Konva.document.getElementsByClassName(className)[0]; + } else { + var id; + if (container.charAt(0) !== '#') { + id = container; + } else { + id = container.slice(1); + } + container = Konva.document.getElementById(id); + } + if (!container) { + throw 'Can not find container in document with id ' + id; + } + } + this._setAttr(CONTAINER, container); + return this; + }, + shouldDrawHit: function() { + return true; + }, + draw: function() { + Konva.Node.prototype.draw.call(this); + return this; + }, + /** * draw layer scene graphs * @name draw * @method * @memberof Konva.Stage.prototype */ - /** + /** * draw layer hit graphs * @name drawHit * @method * @memberof Konva.Stage.prototype */ - /** + /** * set height * @method * @memberof Konva.Stage.prototype * @param {Number} height */ - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - this._resizeDOM(); - return this; - }, - /** + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + this._resizeDOM(); + return this; + }, + /** * set width * @method * @memberof Konva.Stage.prototype * @param {Number} width */ - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - this._resizeDOM(); - return this; - }, - /** + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + this._resizeDOM(); + return this; + }, + /** * clear all layers * @method * @memberof Konva.Stage.prototype */ - clear: function() { - var layers = this.children, - len = layers.length, - n; + clear: function() { + var layers = this.children, len = layers.length, n; - for(n = 0; n < len; n++) { - layers[n].clear(); - } - return this; - }, - clone: function(obj) { - if (!obj) { - obj = {}; - } - obj.container = Konva.document.createElement(DIV); - return Konva.Container.prototype.clone.call(this, obj); - }, - /** + for (n = 0; n < len; n++) { + layers[n].clear(); + } + return this; + }, + clone: function(obj) { + if (!obj) { + obj = {}; + } + obj.container = Konva.document.createElement(DIV); + return Konva.Container.prototype.clone.call(this, obj); + }, + /** * destroy stage * @method * @memberof Konva.Stage.prototype */ - destroy: function() { - var content = this.content; - Konva.Container.prototype.destroy.call(this); + destroy: function() { + var content = this.content; + Konva.Container.prototype.destroy.call(this); - if(content && Konva.Util._isInDocument(content)) { - this.getContainer().removeChild(content); - } - var index = Konva.stages.indexOf(this); - if (index > -1) { - Konva.stages.splice(index, 1); - } - return this; - }, - /** + if (content && Konva.Util._isInDocument(content)) { + this.getContainer().removeChild(content); + } + var index = Konva.stages.indexOf(this); + if (index > -1) { + Konva.stages.splice(index, 1); + } + return this; + }, + /** * get pointer position which can be a touch position or mouse position * @method * @memberof Konva.Stage.prototype * @returns {Object} */ - getPointerPosition: function() { - return this.pointerPos; - }, - getStage: function() { - return this; - }, - /** + getPointerPosition: function() { + return this.pointerPos; + }, + getStage: function() { + return this; + }, + /** * get stage content div element which has the * the class name "konvajs-content" * @method * @memberof Konva.Stage.prototype */ - getContent: function() { - return this.content; - }, - /** + getContent: function() { + return this.content; + }, + /** * Creates a composite data URL * @method * @memberof Konva.Stage.prototype @@ -9009,41 +9309,46 @@ * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 * is very high quality */ - toDataURL: function(config) { - config = config || {}; + toDataURL: function(config) { + config = config || {}; - var mimeType = config.mimeType || null, - quality = config.quality || null, - x = config.x || 0, - y = config.y || 0, - canvas = new Konva.SceneCanvas({ - width: config.width || this.getWidth(), - height: config.height || this.getHeight(), - pixelRatio: config.pixelRatio - }), - _context = canvas.getContext()._context, - layers = this.children; + var mimeType = config.mimeType || null, + quality = config.quality || null, + x = config.x || 0, + y = config.y || 0, + canvas = new Konva.SceneCanvas({ + width: config.width || this.getWidth(), + height: config.height || this.getHeight(), + pixelRatio: config.pixelRatio + }), + _context = canvas.getContext()._context, + layers = this.children; - if(x || y) { - _context.translate(-1 * x, -1 * y); - } + if (x || y) { + _context.translate(-1 * x, -1 * y); + } + layers.each(function(layer) { + var width = layer.getCanvas().getWidth(); + var height = layer.getCanvas().getHeight(); + var ratio = layer.getCanvas().getPixelRatio(); + _context.drawImage( + layer.getCanvas()._canvas, + 0, + 0, + width / ratio, + height / ratio + ); + }); + var src = canvas.toDataURL(mimeType, quality); - layers.each(function(layer) { - var width = layer.getCanvas().getWidth(); - var height = layer.getCanvas().getHeight(); - var ratio = layer.getCanvas().getPixelRatio(); - _context.drawImage(layer.getCanvas()._canvas, 0, 0, width / ratio, height / ratio); - }); - var src = canvas.toDataURL(mimeType, quality); + if (config.callback) { + config.callback(src); + } - if (config.callback) { - config.callback(src); - } - - return src; - }, - /** + return src; + }, + /** * converts stage into an image. * @method * @memberof Konva.Stage.prototype @@ -9059,17 +9364,17 @@ * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 * is very high quality */ - toImage: function(config) { - var cb = config.callback; + toImage: function(config) { + var cb = config.callback; - config.callback = function(dataUrl) { - Konva.Util._getImage(dataUrl, function(img) { - cb(img); - }); - }; - this.toDataURL(config); - }, - /** + config.callback = function(dataUrl) { + Konva.Util._getImage(dataUrl, function(img) { + cb(img); + }); + }; + this.toDataURL(config); + }, + /** * get visible intersection shape. This is the preferred * method for determining if a point intersects a shape or not * @method @@ -9084,45 +9389,47 @@ * // or if you interested in shape parent: * var group = stage.getIntersection({x: 50, y: 50}, 'Group'); */ - getIntersection: function(pos, selector) { - var layers = this.getChildren(), - len = layers.length, - end = len - 1, - n, shape; + getIntersection: function(pos, selector) { + var layers = this.getChildren(), + len = layers.length, + end = len - 1, + n, + shape; - for(n = end; n >= 0; n--) { - shape = layers[n].getIntersection(pos, selector); - if (shape) { - return shape; - } - } + for (n = end; n >= 0; n--) { + shape = layers[n].getIntersection(pos, selector); + if (shape) { + return shape; + } + } - return null; - }, - _resizeDOM: function() { - if(this.content) { - var width = this.getWidth(), - height = this.getHeight(), - layers = this.getChildren(), - len = layers.length, - n, layer; + return null; + }, + _resizeDOM: function() { + if (this.content) { + var width = this.getWidth(), + height = this.getHeight(), + layers = this.getChildren(), + len = layers.length, + n, + layer; - // set content dimensions - this.content.style.width = width + PX; - this.content.style.height = height + PX; + // set content dimensions + this.content.style.width = width + PX; + this.content.style.height = height + PX; - this.bufferCanvas.setSize(width, height); - this.bufferHitCanvas.setSize(width, height); + this.bufferCanvas.setSize(width, height); + this.bufferHitCanvas.setSize(width, height); - // set layer dimensions - for(n = 0; n < len; n++) { - layer = layers[n]; - layer.setSize(width, height); - layer.batchDraw(); - } - } - }, - /** + // set layer dimensions + for (n = 0; n < len; n++) { + layer = layers[n]; + layer.setSize(width, height); + layer.batchDraw(); + } + } + }, + /** * add layer or layers to stage * @method * @memberof Konva.Stage.prototype @@ -9130,391 +9437,409 @@ * @example * stage.add(layer1, layer2, layer3); */ - add: function(layer) { - if (arguments.length > 1) { - for (var i = 0; i < arguments.length; i++) { - this.add(arguments[i]); - } - return this; - } - Konva.Container.prototype.add.call(this, layer); - layer._setCanvasSize(this.width(), this.height()); + add: function(layer) { + if (arguments.length > 1) { + for (var i = 0; i < arguments.length; i++) { + this.add(arguments[i]); + } + return this; + } + Konva.Container.prototype.add.call(this, layer); + layer._setCanvasSize(this.width(), this.height()); - // draw layer and append canvas to container - layer.draw(); - this.content.appendChild(layer.canvas._canvas); + // draw layer and append canvas to container + layer.draw(); + this.content.appendChild(layer.canvas._canvas); - // chainable - return this; - }, - getParent: function() { - return null; - }, - getLayer: function() { - return null; - }, - /** + // chainable + return this; + }, + getParent: function() { + return null; + }, + getLayer: function() { + return null; + }, + /** * returns a {@link Konva.Collection} of layers * @method * @memberof Konva.Stage.prototype */ - getLayers: function() { - return this.getChildren(); - }, - _bindContentEvents: function() { - for (var n = 0; n < eventsLength; n++) { - addEvent(this, EVENTS[n]); - } - }, - _mouseover: function(evt) { - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - this._fire(CONTENT_MOUSEOVER, {evt: evt}); - } - }, - _mouseout: function(evt) { - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var targetShape = this.targetShape; + getLayers: function() { + return this.getChildren(); + }, + _bindContentEvents: function() { + for (var n = 0; n < eventsLength; n++) { + addEvent(this, EVENTS[n]); + } + }, + _mouseover: function(evt) { + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + this._fire(CONTENT_MOUSEOVER, { evt: evt }); + } + }, + _mouseout: function(evt) { + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var targetShape = this.targetShape; - if(targetShape && !Konva.isDragging()) { - targetShape._fireAndBubble(MOUSEOUT, {evt: evt}); - targetShape._fireAndBubble(MOUSELEAVE, {evt: evt}); - this.targetShape = null; - } - this.pointerPos = undefined; + if (targetShape && !Konva.isDragging()) { + targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); + targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); + this.targetShape = null; + } + this.pointerPos = undefined; - this._fire(CONTENT_MOUSEOUT, {evt: evt}); - } - }, - _mousemove: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchmove(evt); - } - // workaround fake mousemove event in chrome browser https://code.google.com/p/chromium/issues/detail?id=161464 - if ((typeof evt.movementX !== 'undefined' || typeof evt.movementY !== 'undefined') && evt.movementY === 0 && evt.movementX === 0) { - return null; - } - if (Konva.UA.mobile) { - return null; - } - this._setPointerPosition(evt); - var shape; + this._fire(CONTENT_MOUSEOUT, { evt: evt }); + } + }, + _mousemove: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchmove(evt); + } + // workaround fake mousemove event in chrome browser https://code.google.com/p/chromium/issues/detail?id=161464 + if ( + (typeof evt.movementX !== 'undefined' || + typeof evt.movementY !== 'undefined') && + evt.movementY === 0 && + evt.movementX === 0 + ) { + return null; + } + if (Konva.UA.mobile) { + return null; + } + this._setPointerPosition(evt); + var shape; - if (!Konva.isDragging()) { - shape = this.getIntersection(this.getPointerPosition()); - if(shape && shape.isListening()) { - if(!Konva.isDragging() && (!this.targetShape || this.targetShape._id !== shape._id)) { - if(this.targetShape) { - this.targetShape._fireAndBubble(MOUSEOUT, {evt: evt}, shape); - this.targetShape._fireAndBubble(MOUSELEAVE, {evt: evt}, shape); - } - shape._fireAndBubble(MOUSEOVER, {evt: evt}, this.targetShape); - shape._fireAndBubble(MOUSEENTER, {evt: evt}, this.targetShape); - this.targetShape = shape; - } - else { - shape._fireAndBubble(MOUSEMOVE, {evt: evt}); - } - } - /* + if (!Konva.isDragging()) { + shape = this.getIntersection(this.getPointerPosition()); + if (shape && shape.isListening()) { + if ( + !Konva.isDragging() && + (!this.targetShape || this.targetShape._id !== shape._id) + ) { + if (this.targetShape) { + this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }, shape); + this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }, shape); + } + shape._fireAndBubble(MOUSEOVER, { evt: evt }, this.targetShape); + shape._fireAndBubble(MOUSEENTER, { evt: evt }, this.targetShape); + this.targetShape = shape; + } else { + shape._fireAndBubble(MOUSEMOVE, { evt: evt }); + } + } else { + /* * if no shape was detected, clear target shape and try * to run mouseout from previous target shape */ - else { - if(this.targetShape && !Konva.isDragging()) { - this.targetShape._fireAndBubble(MOUSEOUT, {evt: evt}); - this.targetShape._fireAndBubble(MOUSELEAVE, {evt: evt}); - this.targetShape = null; - } - - } - - // content event - this._fire(CONTENT_MOUSEMOVE, {evt: evt}); - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _mousedown: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchstart(evt); - } - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - Konva.listenClickTap = true; - - if (shape && shape.isListening()) { - this.clickStartShape = shape; - shape._fireAndBubble(MOUSEDOWN, {evt: evt}); - } - - // content event - this._fire(CONTENT_MOUSEDOWN, {evt: evt}); - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _mouseup: function(evt) { - - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchend(evt); - } - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()), - clickStartShape = this.clickStartShape, - fireDblClick = false, - dd = Konva.DD; - - if(Konva.inDblClickWindow) { - fireDblClick = true; - Konva.inDblClickWindow = false; - } - // don't set inDblClickWindow after dragging - else if (!dd || !dd.justDragged) { - Konva.inDblClickWindow = true; - } else if (dd) { - dd.justDragged = false; - } - - setTimeout(function() { - Konva.inDblClickWindow = false; - }, Konva.dblClickWindow); - - if (shape && shape.isListening()) { - shape._fireAndBubble(MOUSEUP, {evt: evt}); - - // detect if click or double click occurred - if(Konva.listenClickTap && clickStartShape && clickStartShape._id === shape._id) { - shape._fireAndBubble(CLICK, {evt: evt}); - - if(fireDblClick) { - shape._fireAndBubble(DBL_CLICK, {evt: evt}); - } - } - } - // content events - this._fire(CONTENT_MOUSEUP, {evt: evt}); - if (Konva.listenClickTap) { - this._fire(CONTENT_CLICK, {evt: evt}); - if(fireDblClick) { - this._fire(CONTENT_DBL_CLICK, {evt: evt}); - } - } - - Konva.listenClickTap = false; - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _contextmenu: function(evt) { - this._fire(CONTENT_CONTEXTMENU, { evt: evt }); - }, - _touchstart: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - Konva.listenClickTap = true; - - if (shape && shape.isListening()) { - this.tapStartShape = shape; - shape._fireAndBubble(TOUCHSTART, {evt: evt}); - - // only call preventDefault if the shape is listening for events - if (shape.isListening() && shape.preventDefault() && evt.preventDefault) { - evt.preventDefault(); - } - } - // content event - this._fire(CONTENT_TOUCHSTART, {evt: evt}); - }, - _touchend: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()), - fireDblClick = false; - - if(Konva.inDblClickWindow) { - fireDblClick = true; - Konva.inDblClickWindow = false; - } - else { - Konva.inDblClickWindow = true; - } - - setTimeout(function() { - Konva.inDblClickWindow = false; - }, Konva.dblClickWindow); - - if (shape && shape.isListening()) { - shape._fireAndBubble(TOUCHEND, {evt: evt}); - - // detect if tap or double tap occurred - if(Konva.listenClickTap && this.tapStartShape && shape._id === this.tapStartShape._id) { - shape._fireAndBubble(TAP, {evt: evt}); - - if(fireDblClick) { - shape._fireAndBubble(DBL_TAP, {evt: evt}); - } - } - // only call preventDefault if the shape is listening for events - if (shape.isListening() && shape.preventDefault() && evt.preventDefault) { - evt.preventDefault(); - } - } - // content events - this._fire(CONTENT_TOUCHEND, {evt: evt}); - if (Konva.listenClickTap) { - this._fire(CONTENT_TAP, {evt: evt}); - if(fireDblClick) { - this._fire(CONTENT_DBL_TAP, {evt: evt}); - } - } - - Konva.listenClickTap = false; - }, - _touchmove: function(evt) { - this._setPointerPosition(evt); - var dd = Konva.DD, - shape; - if (!Konva.isDragging()) { - shape = this.getIntersection(this.getPointerPosition()); - if (shape && shape.isListening()) { - shape._fireAndBubble(TOUCHMOVE, {evt: evt}); - // only call preventDefault if the shape is listening for events - if (shape.isListening() && shape.preventDefault() && evt.preventDefault) { - evt.preventDefault(); - } - } - this._fire(CONTENT_TOUCHMOVE, {evt: evt}); - } - if(dd) { - if (Konva.isDragging() && Konva.DD.node.preventDefault()) { - evt.preventDefault(); - } - } - }, - _DOMMouseScroll: function(evt) { - this._mousewheel(evt); - }, - _mousewheel: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - if (shape && shape.isListening()) { - shape._fireAndBubble(WHEEL, {evt: evt}); - } - this._fire(CONTENT_WHEEL, {evt: evt}); - }, - _wheel: function(evt) { - this._mousewheel(evt); - }, - _setPointerPosition: function(evt) { - var contentPosition = this._getContentPosition(), - x = null, - y = null; - evt = evt ? evt : window.event; - - // touch events - if(evt.touches !== undefined) { - // currently, only handle one finger - if (evt.touches.length > 0) { - - var touch = evt.touches[0]; - // get the information for finger #1 - x = touch.clientX - contentPosition.left; - y = touch.clientY - contentPosition.top; - } - } - // mouse events - else { - x = evt.clientX - contentPosition.left; - y = evt.clientY - contentPosition.top; - } - if (x !== null && y !== null) { - this.pointerPos = { - x: x, - y: y - }; - } - }, - _getContentPosition: function() { - var rect = this.content.getBoundingClientRect ? this.content.getBoundingClientRect() : { top: 0, left: 0 }; - return { - top: rect.top, - left: rect.left - }; - }, - _buildDOM: function() { - var container = this.getContainer(); - if (!container) { - if (Konva.Util.isBrowser()) { - throw 'Stage has no container. A container is required.'; - } else { - // automatically create element for jsdom in nodejs env - container = Konva.document.createElement(DIV); - } - } - // clear content inside container - container.innerHTML = EMPTY_STRING; - - // content - this.content = Konva.document.createElement(DIV); - this.content.style.position = RELATIVE; - this.content.className = KONVA_CONTENT; - this.content.setAttribute('role', 'presentation'); - container.appendChild(this.content); - - // the buffer canvas pixel ratio must be 1 because it is used as an - // intermediate canvas before copying the result onto a scene canvas. - // not setting it to 1 will result in an over compensation - this.bufferCanvas = new Konva.SceneCanvas(); - this.bufferHitCanvas = new Konva.HitCanvas({pixelRatio: 1}); - - this._resizeDOM(); - }, - _onContent: function(typesStr, handler) { - var types = typesStr.split(SPACE), - len = types.length, - n, baseEvent; - - for(n = 0; n < len; n++) { - baseEvent = types[n]; - this.content.addEventListener(baseEvent, handler, false); - } - }, - // currently cache function is now working for stage, because stage has no its own canvas element - // TODO: may be it is better to cache all children layers? - cache: function() { - Konva.Util.warn('Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.'); - }, - clearCache: function() { + if (this.targetShape && !Konva.isDragging()) { + this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); + this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); + this.targetShape = null; + } } - }); - Konva.Util.extend(Konva.Stage, Konva.Container); - // add getters and setters - Konva.Factory.addGetter(Konva.Stage, 'container'); - Konva.Factory.addOverloadedGetterSetter(Konva.Stage, 'container'); + // content event + this._fire(CONTENT_MOUSEMOVE, { evt: evt }); + } - /** + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mousedown: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchstart(evt); + } + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Konva.listenClickTap = true; + + if (shape && shape.isListening()) { + this.clickStartShape = shape; + shape._fireAndBubble(MOUSEDOWN, { evt: evt }); + } + + // content event + this._fire(CONTENT_MOUSEDOWN, { evt: evt }); + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mouseup: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchend(evt); + } + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()), + clickStartShape = this.clickStartShape, + fireDblClick = false, + dd = Konva.DD; + + if (Konva.inDblClickWindow) { + fireDblClick = true; + Konva.inDblClickWindow = false; + } else if (!dd || !dd.justDragged) { + // don't set inDblClickWindow after dragging + Konva.inDblClickWindow = true; + } else if (dd) { + dd.justDragged = false; + } + + setTimeout(function() { + Konva.inDblClickWindow = false; + }, Konva.dblClickWindow); + + if (shape && shape.isListening()) { + shape._fireAndBubble(MOUSEUP, { evt: evt }); + + // detect if click or double click occurred + if ( + Konva.listenClickTap && + clickStartShape && + clickStartShape._id === shape._id + ) { + shape._fireAndBubble(CLICK, { evt: evt }); + + if (fireDblClick) { + shape._fireAndBubble(DBL_CLICK, { evt: evt }); + } + } + } + // content events + this._fire(CONTENT_MOUSEUP, { evt: evt }); + if (Konva.listenClickTap) { + this._fire(CONTENT_CLICK, { evt: evt }); + if (fireDblClick) { + this._fire(CONTENT_DBL_CLICK, { evt: evt }); + } + } + + Konva.listenClickTap = false; + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _contextmenu: function(evt) { + this._fire(CONTENT_CONTEXTMENU, { evt: evt }); + }, + _touchstart: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Konva.listenClickTap = true; + + if (shape && shape.isListening()) { + this.tapStartShape = shape; + shape._fireAndBubble(TOUCHSTART, { evt: evt }); + + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && + shape.preventDefault() && + evt.preventDefault + ) { + evt.preventDefault(); + } + } + // content event + this._fire(CONTENT_TOUCHSTART, { evt: evt }); + }, + _touchend: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()), + fireDblClick = false; + + if (Konva.inDblClickWindow) { + fireDblClick = true; + Konva.inDblClickWindow = false; + } else { + Konva.inDblClickWindow = true; + } + + setTimeout(function() { + Konva.inDblClickWindow = false; + }, Konva.dblClickWindow); + + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHEND, { evt: evt }); + + // detect if tap or double tap occurred + if ( + Konva.listenClickTap && + this.tapStartShape && + shape._id === this.tapStartShape._id + ) { + shape._fireAndBubble(TAP, { evt: evt }); + + if (fireDblClick) { + shape._fireAndBubble(DBL_TAP, { evt: evt }); + } + } + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && + shape.preventDefault() && + evt.preventDefault + ) { + evt.preventDefault(); + } + } + // content events + this._fire(CONTENT_TOUCHEND, { evt: evt }); + if (Konva.listenClickTap) { + this._fire(CONTENT_TAP, { evt: evt }); + if (fireDblClick) { + this._fire(CONTENT_DBL_TAP, { evt: evt }); + } + } + + Konva.listenClickTap = false; + }, + _touchmove: function(evt) { + this._setPointerPosition(evt); + var dd = Konva.DD, shape; + if (!Konva.isDragging()) { + shape = this.getIntersection(this.getPointerPosition()); + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHMOVE, { evt: evt }); + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && + shape.preventDefault() && + evt.preventDefault + ) { + evt.preventDefault(); + } + } + this._fire(CONTENT_TOUCHMOVE, { evt: evt }); + } + if (dd) { + if (Konva.isDragging() && Konva.DD.node.preventDefault()) { + evt.preventDefault(); + } + } + }, + _DOMMouseScroll: function(evt) { + this._mousewheel(evt); + }, + _mousewheel: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + if (shape && shape.isListening()) { + shape._fireAndBubble(WHEEL, { evt: evt }); + } + this._fire(CONTENT_WHEEL, { evt: evt }); + }, + _wheel: function(evt) { + this._mousewheel(evt); + }, + _setPointerPosition: function(evt) { + var contentPosition = this._getContentPosition(), x = null, y = null; + evt = evt ? evt : window.event; + + // touch events + if (evt.touches !== undefined) { + // currently, only handle one finger + if (evt.touches.length > 0) { + var touch = evt.touches[0]; + // get the information for finger #1 + x = touch.clientX - contentPosition.left; + y = touch.clientY - contentPosition.top; + } + } else { + // mouse events + x = evt.clientX - contentPosition.left; + y = evt.clientY - contentPosition.top; + } + if (x !== null && y !== null) { + this.pointerPos = { + x: x, + y: y + }; + } + }, + _getContentPosition: function() { + var rect = this.content.getBoundingClientRect + ? this.content.getBoundingClientRect() + : { top: 0, left: 0 }; + return { + top: rect.top, + left: rect.left + }; + }, + _buildDOM: function() { + var container = this.getContainer(); + if (!container) { + if (Konva.Util.isBrowser()) { + throw 'Stage has no container. A container is required.'; + } else { + // automatically create element for jsdom in nodejs env + container = Konva.document.createElement(DIV); + } + } + // clear content inside container + container.innerHTML = EMPTY_STRING; + + // content + this.content = Konva.document.createElement(DIV); + this.content.style.position = RELATIVE; + this.content.className = KONVA_CONTENT; + this.content.setAttribute('role', 'presentation'); + container.appendChild(this.content); + + // the buffer canvas pixel ratio must be 1 because it is used as an + // intermediate canvas before copying the result onto a scene canvas. + // not setting it to 1 will result in an over compensation + this.bufferCanvas = new Konva.SceneCanvas(); + this.bufferHitCanvas = new Konva.HitCanvas({ pixelRatio: 1 }); + + this._resizeDOM(); + }, + _onContent: function(typesStr, handler) { + var types = typesStr.split(SPACE), len = types.length, n, baseEvent; + + for (n = 0; n < len; n++) { + baseEvent = types[n]; + this.content.addEventListener(baseEvent, handler, false); + } + }, + // currently cache function is now working for stage, because stage has no its own canvas element + // TODO: may be it is better to cache all children layers? + cache: function() { + Konva.Util.warn( + 'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.' + ); + }, + clearCache: function() {} + }); + Konva.Util.extend(Konva.Stage, Konva.Container); + + // add getters and setters + Konva.Factory.addGetter(Konva.Stage, 'container'); + Konva.Factory.addOverloadedGetterSetter(Konva.Stage, 'container'); + + /** * get container DOM element * @name container * @method @@ -9528,7 +9853,6 @@ * body.appendChild(container); * stage.container(container); */ - })(); (function() { diff --git a/konva.min.js b/konva.min.js index d622ef62..ee20b857 100644 --- a/konva.min.js +++ b/konva.min.js @@ -1,8 +1,8 @@ /* - * Konva JavaScript Framework v1.4.0 + * Konva JavaScript Framework v1.6.3 * http://konvajs.github.io/ * Licensed under the MIT or GPL Version 2 licenses. - * Date: Sun Apr 09 2017 + * Date: Wed Jul 05 2017 * * Original work Copyright (C) 2011 - 2013 by Eric Rowell (KineticJS) * Modified work Copyright (C) 2014 - 2015 by Anton Lavrenov (Konva) @@ -26,8 +26,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -!function(t){"use strict";var e=Math.PI/180,n={version:"1.4.0",stages:[],idCounter:0,ids:{},names:{},shapes:{},listenClickTap:!1,inDblClickWindow:!1,enableTrace:!1,traceArrMax:100,dblClickWindow:400,pixelRatio:void 0,dragDistance:0,angleDeg:!0,showWarnings:!0,Filters:{},isDragging:function(){var t=n.DD;return!!t&&t.isDragging},isDragReady:function(){var t=n.DD;return!!t&&!!t.node},_addId:function(t,e){void 0!==e&&(this.ids[e]=t)},_removeId:function(t){void 0!==t&&delete this.ids[t]},_addName:function(t,e){e&&(this.names[e]||(this.names[e]=[]),this.names[e].push(t))},_removeName:function(t,e){if(t){var n=this.names[t];if(n){for(var i=0;i0)return parseInt(t.substring(e+5,t.indexOf(".",e)),10);if(t.indexOf("trident/")>0){var n=t.indexOf("rv:");return parseInt(t.substring(n+3,t.indexOf(".",n)),10)}var i=t.indexOf("edge/");return i>0&&parseInt(t.substring(i+5,t.indexOf(".",i)),10)},_parseUA:function(t){var e=t.toLowerCase(),i=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[],a=!!t.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i),r=!!t.match(/IEMobile/i);return{browser:i[1]||"",version:i[2]||"0",isIE:n._detectIE(e),mobile:a,ieMobile:r}},UA:void 0},i=void 0!==t?t:"undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope?self:{};if(n.UA=n._parseUA(i.navigator&&i.navigator.userAgent||""),i.Konva&&console.error("Konva instance is already exist in current eviroment. Please use only one instance."),i.Konva=n,n.global=i,"object"==typeof exports){if(i.window&&i.window.document)n.document=i.window.document,n.window=i.window;else{var a=require("canvas"),r=require("jsdom").jsdom;n.window=r("").defaultView,n.document=n.window.document,n.window.Image=a.Image,n._nodeCanvas=a}return void(module.exports=n)}"function"==typeof define&&define.amd&&define(function(){return n}),n.document=document,n.window=window}("undefined"!=typeof global?global:window),function(){"use strict";Konva.Collection=function(){var t=[].slice.call(arguments),e=t.length,n=0;for(this.length=e;n0},isValidSelector:function(t){if("string"!=typeof t)return!1;var e=t[0];return"#"===e||"."===e||e===e.toUpperCase()},createCanvasElement:function(){var t=Konva.document.createElement("canvas");try{t.style=t.style||{}}catch(t){}return t},isBrowser:function(){return"object"!=typeof exports},_isInDocument:function(t){for(;t=t.parentNode;)if(t==Konva.document)return!0;return!1},_simplifyArray:function(t){var e,n,i=[],a=t.length,r=Konva.Util;for(e=0;e>16&255,g:e>>8&255,b:255&e}},getRandomColor:function(){for(var t=(16777215*Math.random()<<0).toString(16);t.length<6;)t="0"+t;return"#"+t},get:function(t,e){return void 0===t?e:t},getRGB:function(t){var e;return t in n?(e=n[t],{r:e[0],g:e[1],b:e[2]}):"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=i.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:function(t){return t=t||"black",Konva.Util._namedColorToRBA(t)||Konva.Util._hex3ColorToRGBA(t)||Konva.Util._hex6ColorToRGBA(t)||Konva.Util._rgbColorToRGBA(t)||Konva.Util._rgbaColorToRGBA(t)},_namedColorToRBA:function(t){var e=n[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA:function(t){if(0===t.indexOf("rgb(")){t=t.match(/rgb\(([^)]+)\)/)[1];var e=t.split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA:function(t){if(0===t.indexOf("rgba(")){t=t.match(/rgba\(([^)]+)\)/)[1];var e=t.split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex6ColorToRGBA:function(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex3ColorToRGBA:function(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},_merge:function(t,e){var n=this._clone(e);for(var i in t)this._isObject(t[i])?n[i]=this._merge(t[i],n[i]):n[i]=t[i];return n},cloneObject:function(t){var e={};for(var n in t)this._isObject(t[n])?e[n]=this.cloneObject(t[n]):this._isArray(t[n])?e[n]=this.cloneArray(t[n]):e[n]=t[n];return e},cloneArray:function(t){return t.slice(0)},_degToRad:function(e){return e*t},_radToDeg:function(t){return t*e},_capitalize:function(t){return t.charAt(0).toUpperCase()+t.slice(1)},throw:function(t){throw new Error("Konva error: "+t)},error:function(t){console.error("Konva error: "+t)},warn:function(t){Konva.global.console&&console.warn&&Konva.showWarnings&&console.warn("Konva warning: "+t)},extend:function(t,e){function n(){this.constructor=t}n.prototype=e.prototype;var i=t.prototype;t.prototype=new n;for(var a in i)i.hasOwnProperty(a)&&(t.prototype[a]=i[a]);t.__super__=e.prototype,t.super=e},addMethods:function(t,e){var n;for(n in e)t.prototype[n]=e[n]},_getControlPoints:function(t,e,n,i,a,r,o){var s=Math.sqrt(Math.pow(n-t,2)+Math.pow(i-e,2)),h=Math.sqrt(Math.pow(a-n,2)+Math.pow(r-i,2)),c=o*s/(s+h),l=o*h/(s+h);return[n-c*(a-t),i-c*(r-e),n+l*(a-t),i+l*(r-e)]},_expandPoints:function(t,e){var n,i,a=t.length,r=[];for(n=2;n1?(o=n,s=i,h=(n-a)*(n-a)+(i-r)*(i-r)):(o=t+l*(n-t),s=e+l*(i-e),h=(o-a)*(o-a)+(s-r)*(s-r))}return[o,s,h]},_getProjectionToLine:function(t,e,n){var i=Konva.Util.cloneObject(t),a=Number.MAX_VALUE;return e.forEach(function(r,o){if(n||o!==e.length-1){var s=e[(o+1)%e.length],h=Konva.Util._getProjectionToSegment(r.x,r.y,s.x,s.y,t.x,t.y),c=h[0],l=h[1],d=h[2];de.length){var o=e;e=t,t=o}for(i=0;i=Konva.traceArrMax&&n.shift()},reset:function(){var t=this.getCanvas().getPixelRatio();this.setTransform(1*t,0,0,1*t,0,0)},getCanvas:function(){return this.canvas},clear:function(t){var e=this.getCanvas();t?this.clearRect(t.x||0,t.y||0,t.width||0,t.height||0):this.clearRect(0,0,e.getWidth()/e.pixelRatio,e.getHeight()/e.pixelRatio)},_applyLineCap:function(t){var e=t.getLineCap();e&&this.setAttr("lineCap",e)},_applyOpacity:function(t){var e=t.getAbsoluteOpacity();1!==e&&this.setAttr("globalAlpha",e)},_applyLineJoin:function(t){var e=t.getLineJoin();e&&this.setAttr("lineJoin",e)},setAttr:function(t,e){this._context[t]=e},arc:function(){var t=arguments;this._context.arc(t[0],t[1],t[2],t[3],t[4],t[5])},beginPath:function(){this._context.beginPath()},bezierCurveTo:function(){var t=arguments;this._context.bezierCurveTo(t[0],t[1],t[2],t[3],t[4],t[5])},clearRect:function(){var t=arguments;this._context.clearRect(t[0],t[1],t[2],t[3])},clip:function(){this._context.clip()},closePath:function(){this._context.closePath()},createImageData:function(){var t=arguments;return 2===t.length?this._context.createImageData(t[0],t[1]):1===t.length?this._context.createImageData(t[0]):void 0},createLinearGradient:function(){var t=arguments;return this._context.createLinearGradient(t[0],t[1],t[2],t[3])},createPattern:function(){var t=arguments;return this._context.createPattern(t[0],t[1])},createRadialGradient:function(){var t=arguments;return this._context.createRadialGradient(t[0],t[1],t[2],t[3],t[4],t[5])},drawImage:function(){var t=arguments,e=this._context;3===t.length?e.drawImage(t[0],t[1],t[2]):5===t.length?e.drawImage(t[0],t[1],t[2],t[3],t[4]):9===t.length&&e.drawImage(t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8])},isPointInPath:function(t,e){return this._context.isPointInPath(t,e)},fill:function(){this._context.fill()},fillRect:function(t,e,n,i){this._context.fillRect(t,e,n,i)},strokeRect:function(t,e,n,i){this._context.strokeRect(t,e,n,i)},fillText:function(){var t=arguments;this._context.fillText(t[0],t[1],t[2])},measureText:function(t){return this._context.measureText(t)},getImageData:function(){var t=arguments;return this._context.getImageData(t[0],t[1],t[2],t[3])},lineTo:function(){var t=arguments;this._context.lineTo(t[0],t[1])},moveTo:function(){var t=arguments;this._context.moveTo(t[0],t[1])},rect:function(){var t=arguments;this._context.rect(t[0],t[1],t[2],t[3])},putImageData:function(){var t=arguments;this._context.putImageData(t[0],t[1],t[2])},quadraticCurveTo:function(){var t=arguments;this._context.quadraticCurveTo(t[0],t[1],t[2],t[3])},restore:function(){this._context.restore()},rotate:function(){var t=arguments;this._context.rotate(t[0])},save:function(){this._context.save()},scale:function(){var t=arguments;this._context.scale(t[0],t[1])},setLineDash:function(){var t=arguments,e=this._context;this._context.setLineDash?e.setLineDash(t[0]):"mozDash"in e?e.mozDash=t[0]:"webkitLineDash"in e&&(e.webkitLineDash=t[0])},getLineDash:function(){return this._context.getLineDash()},setTransform:function(){var t=arguments;this._context.setTransform(t[0],t[1],t[2],t[3],t[4],t[5])},stroke:function(){this._context.stroke()},strokeText:function(){var t=arguments;this._context.strokeText(t[0],t[1],t[2])},transform:function(){var t=arguments;this._context.transform(t[0],t[1],t[2],t[3],t[4],t[5])},translate:function(){var t=arguments;this._context.translate(t[0],t[1])},_enableTrace:function(){var e,n,i=this,a=t.length,r=Konva.Util._simplifyArray,o=this.setAttr;for(e=0;e255?255:t<0?0:Math.round(t)},alphaComponent:function(t){return t>1?1:t<1e-4?1e-4:t}}}(),function(t){"use strict";var e="Shape",n=["id"],i=["xChange.konva","yChange.konva","scaleXChange.konva","scaleYChange.konva","skewXChange.konva","skewYChange.konva","rotationChange.konva","offsetXChange.konva","offsetYChange.konva","transformsEnabledChange.konva"].join(" "),a=["scaleXChange.konva","scaleYChange.konva"].join(" ");t.Node=function(t){this._init(t)},t.Util.addMethods(t.Node,{_init:function(e){var n=this;this._id=t.idCounter++,this.eventListeners={},this.attrs={},this._cache={},this._filterUpToDate=!1,this._isUnderCache=!1,this.setAttrs(e),this.on(i,function(){this._clearCache("transform"),n._clearSelfAndDescendantCache("absoluteTransform")}),this.on(a,function(){n._clearSelfAndDescendantCache("absoluteScale")}),this.on("visibleChange.konva",function(){n._clearSelfAndDescendantCache("visible")}),this.on("listeningChange.konva",function(){n._clearSelfAndDescendantCache("listening")}),this.on("opacityChange.konva",function(){n._clearSelfAndDescendantCache("absoluteOpacity")})},_clearCache:function(t){t?delete this._cache[t]:this._cache={}},_getCache:function(t,e){return void 0===this._cache[t]&&(this._cache[t]=e.call(this)),this._cache[t]},_clearSelfAndDescendantCache:function(t){this._clearCache(t),this.children&&this.getChildren().each(function(e){e._clearSelfAndDescendantCache(t)})},clearCache:function(){return delete this._cache.canvas,this._filterUpToDate=!1,this},cache:function(e){var n=e||{},i=this.getClientRect(!0),a=n.width||i.width,r=n.height||i.height,o=n.pixelRatio,s=n.x||i.x,h=n.y||i.y,c=n.offset||0,l=n.drawBorder||!1;if(!a||!r)throw new Error("Width or height of caching configuration equals 0.");a+=2*c,r+=2*c,s-=c,h-=c;var d=new t.SceneCanvas({pixelRatio:o,width:a,height:r}),u=new t.SceneCanvas({pixelRatio:o,width:a,height:r}),v=new t.HitCanvas({pixelRatio:1,width:a,height:r}),f=d.getContext(),g=v.getContext();return v.isCache=!0,this.clearCache(),f.save(),g.save(),f.translate(-s,-h),g.translate(-s,-h),this._isUnderCache=!0,this._clearSelfAndDescendantCache("absoluteOpacity"),this._clearSelfAndDescendantCache("absoluteScale"),this.drawScene(d,this,!0),this.drawHit(v,this,!0),this._isUnderCache=!1,f.restore(),g.restore(),l&&(f.save(),f.beginPath(),f.rect(0,0,a,r),f.closePath(),f.setAttr("strokeStyle","red"),f.setAttr("lineWidth",5),f.stroke(),f.restore()),this._cache.canvas={scene:d,filter:u,hit:v,x:s,y:h},this},getClientRect:function(){throw new Error('abstract "getClientRect" method call')},_transformedRect:function(t){var e,n,i,a,r=[{x:t.x,y:t.y},{x:t.x+t.width,y:t.y},{x:t.x+t.width,y:t.y+t.height},{x:t.x,y:t.y+t.height}],o=this.getTransform();return r.forEach(function(t){var r=o.point(t);void 0===e&&(e=i=r.x,n=a=r.y),e=Math.min(e,r.x),n=Math.min(n,r.y),i=Math.max(i,r.x),a=Math.max(a,r.y)}),{x:e,y:n,width:i-e,height:a-n}},_drawCachedSceneCanvas:function(t){t.save(),t._applyOpacity(this),t.translate(this._cache.canvas.x,this._cache.canvas.y);var e=this._getCachedSceneCanvas(),n=e.pixelRatio;t.drawImage(e._canvas,0,0,e.width/n,e.height/n),t.restore()},_drawCachedHitCanvas:function(t){var e=this._cache.canvas,n=e.hit;t.save(),t.translate(this._cache.canvas.x,this._cache.canvas.y),t.drawImage(n._canvas,0,0),t.restore()},_getCachedSceneCanvas:function(){var e,n,i,a,r=this.filters(),o=this._cache.canvas,s=o.scene,h=o.filter,c=h.getContext();if(r){if(!this._filterUpToDate){var l=s.pixelRatio;try{for(e=r.length,c.clear(),c.drawImage(s._canvas,0,0,s.getWidth()/l,s.getHeight()/l),n=c.getImageData(0,0,h.getWidth(),h.getHeight()),i=0;i0&&n[0].getDepth()<=o&&t(n)}var n,i,a,r,o=this.getDepth(),s=this,h=0;return"Stage"!==s.nodeType&&t(s.getStage().getChildren()),h},getDepth:function(){for(var t=0,e=this.parent;e;)t++,e=e.parent;return t},setPosition:function(t){return this.setX(t.x),this.setY(t.y),this},getPosition:function(){return{x:this.getX(),y:this.getY()}},getAbsolutePosition:function(e){var n=this.getAbsoluteTransform(e).getMatrix(),i=new t.Transform,a=this.offset();return i.m=n.slice(),i.translate(a.x,a.y),i.getTranslation()},setAbsolutePosition:function(t){var e,n=this._clearTransform();return this.attrs.x=n.x,this.attrs.y=n.y,delete n.x,delete n.y,e=this.getAbsoluteTransform(),e.invert(),e.translate(t.x,t.y),t={x:this.attrs.x+e.getTranslation().x,y:this.attrs.y+e.getTranslation().y},this.setPosition({x:t.x,y:t.y}),this._setTransform(n),this},_setTransform:function(t){var e;for(e in t)this.attrs[e]=t[e];this._clearCache("transform"),this._clearSelfAndDescendantCache("absoluteTransform")},_clearTransform:function(){var t={x:this.getX(),y:this.getY(),rotation:this.getRotation(),scaleX:this.getScaleX(),scaleY:this.getScaleY(),offsetX:this.getOffsetX(),offsetY:this.getOffsetY(),skewX:this.getSkewX(),skewY:this.getSkewY()};return this.attrs.x=0,this.attrs.y=0,this.attrs.rotation=0,this.attrs.scaleX=1,this.attrs.scaleY=1,this.attrs.offsetX=0,this.attrs.offsetY=0,this.attrs.skewX=0,this.attrs.skewY=0,this._clearCache("transform"),this._clearSelfAndDescendantCache("absoluteTransform"),t},move:function(t){var e=t.x,n=t.y,i=this.getX(),a=this.getY();return void 0!==e&&(i+=e),void 0!==n&&(a+=n),this.setPosition({x:i,y:a}),this},_eachAncestorReverse:function(t,e){var n,i,a=[],r=this.getParent();if(e&&e._id===this._id)return t(this),!0;for(a.unshift(this);r&&(!e||r._id!==e._id);)a.unshift(r),r=r.parent;for(n=a.length,i=0;i0&&(this.parent.children.splice(e,1),this.parent.children.splice(e-1,0,this),this.parent._setChildrenIndices(),!0)},moveToBottom:function(){if(!this.parent)return t.Util.warn("Node has no parent. moveToBottom function is ignored."),!1;var e=this.index;return e>0&&(this.parent.children.splice(e,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0)},setZIndex:function(e){if(!this.parent)return t.Util.warn("Node has no parent. zIndex parameter is ignored."),!1;var n=this.index;return this.parent.children.splice(n,1),this.parent.children.splice(e,0,this),this.parent._setChildrenIndices(),this},getAbsoluteOpacity:function(){return this._getCache("absoluteOpacity",this._getAbsoluteOpacity)},_getAbsoluteOpacity:function(){var t=this.getOpacity(),e=this.getParent();return e&&!e._isUnderCache&&(t*=this.getParent().getAbsoluteOpacity()),t},moveTo:function(t){return this.getParent()!==t&&((this.__originalRemove||this.remove).call(this),t.add(this)),this},toObject:function(){var e,n,i,a,r={},o=this.getAttrs();r.attrs={};for(e in o)n=o[e],i=this[e],delete o[e],a=i?i.call(this):null,o[e]=n,a!==n&&(r.attrs[e]=n);return r.className=this.getClassName(),t.Util._prepareToStringify(r)},toJSON:function(){return JSON.stringify(this.toObject())},getParent:function(){return this.parent},findAncestors:function(t,e,n){var i=[];e&&this._isMatch(t)&&i.push(this);for(var a=this.parent;a;){if(a===n)return i;a._isMatch(t)&&i.push(a),a=a.parent}return i},findAncestor:function(t,e,n){return this.findAncestors(t,e,n)[0]},_isMatch:function(e){if(!e)return!1;var n,i,a=e.replace(/ /g,"").split(","),r=a.length;for(n=0;n>W,0!==T?(T=255/T,A[l]=(u*H>>W)*T,A[l+1]=(v*H>>W)*T,A[l+2]=(f*H>>W)*T):A[l]=A[l+1]=A[l+2]=0,u-=p,v-=m,f-=_,g-=y,p-=E.r,m-=E.g,_-=E.b,y-=E.a,h=d+((h=r+a+1)>W,T>0?(T=255/T,A[h]=(u*H>>W)*T,A[h+1]=(v*H>>W)*T,A[h+2]=(f*H>>W)*T):A[h]=A[h+1]=A[h+2]=0,u-=p,v-=m,f-=_,g-=y,p-=E.r,m-=E.g,_-=E.b,y-=E.a,h=r+((h=o+L)0&&e(t,n)},Konva.Factory.addGetterSetter(Konva.Node,"blurRadius",0,null,Konva.Factory.afterSetFilter)}(),function(){"use strict";function t(t,e,n){var i=4*(n*t.width+e),a=[];return a.push(t.data[i++],t.data[i++],t.data[i++],t.data[i++]),a}function e(t,e){return Math.sqrt(Math.pow(t[0]-e[0],2)+Math.pow(t[1]-e[1],2)+Math.pow(t[2]-e[2],2))}function n(t){for(var e=[0,0,0],n=0;n=0&&v=0&&f=0&&v=0&&f=1020?255:0}return o}function s(t,e,n){for(var i=[1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9],a=Math.round(Math.sqrt(i.length)),r=Math.floor(a/2),o=[],s=0;s=0&&v=0&&f255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"green",0,function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"blue",0,Konva.Validators.RGBComponent,Konva.Factory.afterSetFilter)}(),function(){"use strict";Konva.Filters.RGBA=function(t){var e,n,i=t.data,a=i.length,r=this.red(),o=this.green(),s=this.blue(),h=this.alpha();for(e=0;e255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"green",0,function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"blue",0,Konva.Validators.RGBComponent,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"alpha",1,function(t){return this._filterUpToDate=!1,t>1?1:t<0?0:t})}(),function(){"use strict";Konva.Filters.HSV=function(t){var e,n,i,a,r,o=t.data,s=o.length,h=Math.pow(2,this.value()),c=Math.pow(2,this.saturation()),l=Math.abs(this.hue()+360)%360,d=h*c*Math.cos(l*Math.PI/180),u=h*c*Math.sin(l*Math.PI/180),v=.299*h+.701*d+.167*u,f=.587*h-.587*d+.33*u,g=.114*h-.114*d-.497*u,p=.299*h-.299*d-.328*u,m=.587*h+.413*d+.035*u,_=.114*h-.114*d+.293*u,y=.299*h-.3*d+1.25*u,K=.587*h-.586*d-1.05*u,S=.114*h+.886*d-.2*u;for(e=0;ec&&(v=0);var f=(d-1+v)*h*4,g=h;do{var p=u+4*(g-1),m=o;g+m<1&&(m=0),g+m>h&&(m=0);var _=f+4*(g-1+m),y=s[p]-s[_],K=s[p+1]-s[_+1],S=s[p+2]-s[_+2],x=y,C=x>0?x:-x,w=K>0?K:-K,b=S>0?S:-S;if(w>C&&(x=K),b>C&&(x=S),x*=e,a){var F=s[p]+x,T=s[p+1]+x,P=s[p+2]+x;s[p]=F>255?255:F<0?0:F,s[p+1]=T>255?255:T<0?0:T,s[p+2]=P>255?255:P<0?0:P}else{var A=n-x;A<0?A=0:A>255&&(A=255),s[p]=s[p+1]=s[p+2]=A}}while(--g)}while(--d)},Konva.Factory.addGetterSetter(Konva.Node,"embossStrength",.5,null,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"embossWhiteLevel",.5,null,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"embossDirection","top-left",null,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"embossBlend",!1,null,Konva.Factory.afterSetFilter)}(),function(){"use strict";function t(t,e,n,i,a){var r,o=n-e,s=a-i;return 0===o?i+s/2:0===s?i:(r=(t-e)/o,r=s*r+i)}Konva.Filters.Enhance=function(e){var n,i,a,r,o=e.data,s=o.length,h=o[0],c=h,l=o[1],d=l,u=o[2],v=u,f=this.enhance();if(0!==f){for(r=0;rc&&(c=n),i=o[r+1],id&&(d=i),a=o[r+2],av&&(v=a);c===h&&(c=255,h=0),d===l&&(d=255,l=0),v===u&&(v=255,u=0);var g,p,m,_,y,K,S,x,C;for(f>0?(p=c+f*(255-c),m=h-f*(h-0),y=d+f*(255-d),K=l-f*(l-0),x=v+f*(255-v),C=u-f*(u-0)):(g=.5*(c+h),p=c+f*(c-g),m=h+f*(h-g),_=.5*(d+l),y=d+f*(d-_),K=l+f*(l-_),S=.5*(v+u),x=v+f*(v-S),C=u+f*(u-S)),r=0;r=p))for(n=l;n=m||(i=4*(p*n+e),a+=t[i+0],r+=t[i+1],o+=t[i+2],s+=t[i+3],f+=1);for(a/=f,r/=f,o/=f,e=h;e=p))for(n=l;n=m||(i=4*(p*n+e),t[i+0]=a,t[i+1]=r,t[i+2]=o,t[i+3]=s)}},Konva.Factory.addGetterSetter(Konva.Node,"pixelSize",8,null,Konva.Factory.afterSetFilter)}(),function(){"use strict";Konva.Filters.Threshold=function(t){var e,n=255*this.threshold(),i=t.data,a=i.length;for(e=0;e0)return parseInt(t.substring(e+5,t.indexOf(".",e)),10);if(t.indexOf("trident/")>0){var n=t.indexOf("rv:");return parseInt(t.substring(n+3,t.indexOf(".",n)),10)}var i=t.indexOf("edge/");return i>0&&parseInt(t.substring(i+5,t.indexOf(".",i)),10)},_parseUA:function(t){var e=t.toLowerCase(),i=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[],a=!!t.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i),r=!!t.match(/IEMobile/i);return{browser:i[1]||"",version:i[2]||"0",isIE:n._detectIE(e),mobile:a,ieMobile:r}},UA:void 0},i=void 0!==t?t:"undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope?self:{};if(n.UA=n._parseUA(i.navigator&&i.navigator.userAgent||""),i.Konva&&console.error("Konva instance is already exist in current eviroment. Please use only one instance."),i.Konva=n,n.global=i,"object"==typeof exports){if(i.window&&i.window.document)n.document=i.window.document,n.window=i.window;else{var a=require("canvas"),r=require("jsdom").jsdom;n.window=r("").defaultView,n.document=n.window.document,n.window.Image=a.Image,n._nodeCanvas=a}return void(module.exports=n)}"function"==typeof define&&define.amd&&define(function(){return n}),n.document=document,n.window=window}("undefined"!=typeof global?global:window),function(){"use strict";Konva.Collection=function(){var t=[].slice.call(arguments),e=t.length,n=0;for(this.length=e;n0},isValidSelector:function(t){if("string"!=typeof t)return!1;var e=t[0];return"#"===e||"."===e||e===e.toUpperCase()},createCanvasElement:function(){var t=Konva.document.createElement("canvas");try{t.style=t.style||{}}catch(t){}return t},isBrowser:function(){return"object"!=typeof exports},_isInDocument:function(t){for(;t=t.parentNode;)if(t==Konva.document)return!0;return!1},_simplifyArray:function(t){var e,n,i=[],a=t.length,r=Konva.Util;for(e=0;e>16&255,g:e>>8&255,b:255&e}},getRandomColor:function(){for(var t=(16777215*Math.random()<<0).toString(16);t.length<6;)t="0"+t;return"#"+t},get:function(t,e){return void 0===t?e:t},getRGB:function(t){var e;return t in n?(e=n[t],{r:e[0],g:e[1],b:e[2]}):"#"===t[0]?this._hexToRgb(t.substring(1)):"rgb("===t.substr(0,4)?(e=i.exec(t.replace(/ /g,"")),{r:parseInt(e[1],10),g:parseInt(e[2],10),b:parseInt(e[3],10)}):{r:0,g:0,b:0}},colorToRGBA:function(t){return t=t||"black",Konva.Util._namedColorToRBA(t)||Konva.Util._hex3ColorToRGBA(t)||Konva.Util._hex6ColorToRGBA(t)||Konva.Util._rgbColorToRGBA(t)||Konva.Util._rgbaColorToRGBA(t)},_namedColorToRBA:function(t){var e=n[t.toLowerCase()];return e?{r:e[0],g:e[1],b:e[2],a:1}:null},_rgbColorToRGBA:function(t){if(0===t.indexOf("rgb(")){t=t.match(/rgb\(([^)]+)\)/)[1];var e=t.split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:1}}},_rgbaColorToRGBA:function(t){if(0===t.indexOf("rgba(")){t=t.match(/rgba\(([^)]+)\)/)[1];var e=t.split(/ *, */).map(Number);return{r:e[0],g:e[1],b:e[2],a:e[3]}}},_hex6ColorToRGBA:function(t){if("#"===t[0]&&7===t.length)return{r:parseInt(t.slice(1,3),16),g:parseInt(t.slice(3,5),16),b:parseInt(t.slice(5,7),16),a:1}},_hex3ColorToRGBA:function(t){if("#"===t[0]&&4===t.length)return{r:parseInt(t[1]+t[1],16),g:parseInt(t[2]+t[2],16),b:parseInt(t[3]+t[3],16),a:1}},_merge:function(t,e){var n=this._clone(e);for(var i in t)this._isObject(t[i])?n[i]=this._merge(t[i],n[i]):n[i]=t[i];return n},cloneObject:function(t){var e={};for(var n in t)this._isObject(t[n])?e[n]=this.cloneObject(t[n]):this._isArray(t[n])?e[n]=this.cloneArray(t[n]):e[n]=t[n];return e},cloneArray:function(t){return t.slice(0)},_degToRad:function(e){return e*t},_radToDeg:function(t){return t*e},_capitalize:function(t){return t.charAt(0).toUpperCase()+t.slice(1)},throw:function(t){throw new Error("Konva error: "+t)},error:function(t){console.error("Konva error: "+t)},warn:function(t){Konva.global.console&&console.warn&&Konva.showWarnings&&console.warn("Konva warning: "+t)},extend:function(t,e){function n(){this.constructor=t}n.prototype=e.prototype;var i=t.prototype;t.prototype=new n;for(var a in i)i.hasOwnProperty(a)&&(t.prototype[a]=i[a]);t.__super__=e.prototype,t.super=e},addMethods:function(t,e){var n;for(n in e)t.prototype[n]=e[n]},_getControlPoints:function(t,e,n,i,a,r,o){var s=Math.sqrt(Math.pow(n-t,2)+Math.pow(i-e,2)),h=Math.sqrt(Math.pow(a-n,2)+Math.pow(r-i,2)),c=o*s/(s+h),l=o*h/(s+h);return[n-c*(a-t),i-c*(r-e),n+l*(a-t),i+l*(r-e)]},_expandPoints:function(t,e){var n,i,a=t.length,r=[];for(n=2;n1?(o=n,s=i,h=(n-a)*(n-a)+(i-r)*(i-r)):(o=t+l*(n-t),s=e+l*(i-e),h=(o-a)*(o-a)+(s-r)*(s-r))}return[o,s,h]},_getProjectionToLine:function(t,e,n){var i=Konva.Util.cloneObject(t),a=Number.MAX_VALUE;return e.forEach(function(r,o){if(n||o!==e.length-1){var s=e[(o+1)%e.length],h=Konva.Util._getProjectionToSegment(r.x,r.y,s.x,s.y,t.x,t.y),c=h[0],l=h[1],d=h[2];de.length){var o=e;e=t,t=o}for(i=0;i=Konva.traceArrMax&&n.shift()},reset:function(){var t=this.getCanvas().getPixelRatio();this.setTransform(1*t,0,0,1*t,0,0)},getCanvas:function(){return this.canvas},clear:function(t){var e=this.getCanvas();t?this.clearRect(t.x||0,t.y||0,t.width||0,t.height||0):this.clearRect(0,0,e.getWidth()/e.pixelRatio,e.getHeight()/e.pixelRatio)},_applyLineCap:function(t){var e=t.getLineCap();e&&this.setAttr("lineCap",e)},_applyOpacity:function(t){var e=t.getAbsoluteOpacity();1!==e&&this.setAttr("globalAlpha",e)},_applyLineJoin:function(t){var e=t.getLineJoin();e&&this.setAttr("lineJoin",e)},setAttr:function(t,e){this._context[t]=e},arc:function(){var t=arguments;this._context.arc(t[0],t[1],t[2],t[3],t[4],t[5])},beginPath:function(){this._context.beginPath()},bezierCurveTo:function(){var t=arguments;this._context.bezierCurveTo(t[0],t[1],t[2],t[3],t[4],t[5])},clearRect:function(){var t=arguments;this._context.clearRect(t[0],t[1],t[2],t[3])},clip:function(){this._context.clip()},closePath:function(){this._context.closePath()},createImageData:function(){var t=arguments;return 2===t.length?this._context.createImageData(t[0],t[1]):1===t.length?this._context.createImageData(t[0]):void 0},createLinearGradient:function(){var t=arguments;return this._context.createLinearGradient(t[0],t[1],t[2],t[3])},createPattern:function(){var t=arguments;return this._context.createPattern(t[0],t[1])},createRadialGradient:function(){var t=arguments;return this._context.createRadialGradient(t[0],t[1],t[2],t[3],t[4],t[5])},drawImage:function(){var t=arguments,e=this._context;3===t.length?e.drawImage(t[0],t[1],t[2]):5===t.length?e.drawImage(t[0],t[1],t[2],t[3],t[4]):9===t.length&&e.drawImage(t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7],t[8])},isPointInPath:function(t,e){return this._context.isPointInPath(t,e)},fill:function(){this._context.fill()},fillRect:function(t,e,n,i){this._context.fillRect(t,e,n,i)},strokeRect:function(t,e,n,i){this._context.strokeRect(t,e,n,i)},fillText:function(){var t=arguments;this._context.fillText(t[0],t[1],t[2])},measureText:function(t){return this._context.measureText(t)},getImageData:function(){var t=arguments;return this._context.getImageData(t[0],t[1],t[2],t[3])},lineTo:function(){var t=arguments;this._context.lineTo(t[0],t[1])},moveTo:function(){var t=arguments;this._context.moveTo(t[0],t[1])},rect:function(){var t=arguments;this._context.rect(t[0],t[1],t[2],t[3])},putImageData:function(){var t=arguments;this._context.putImageData(t[0],t[1],t[2])},quadraticCurveTo:function(){var t=arguments;this._context.quadraticCurveTo(t[0],t[1],t[2],t[3])},restore:function(){this._context.restore()},rotate:function(){var t=arguments;this._context.rotate(t[0])},save:function(){this._context.save()},scale:function(){var t=arguments;this._context.scale(t[0],t[1])},setLineDash:function(){var t=arguments,e=this._context;this._context.setLineDash?e.setLineDash(t[0]):"mozDash"in e?e.mozDash=t[0]:"webkitLineDash"in e&&(e.webkitLineDash=t[0])},getLineDash:function(){return this._context.getLineDash()},setTransform:function(){var t=arguments;this._context.setTransform(t[0],t[1],t[2],t[3],t[4],t[5])},stroke:function(){this._context.stroke()},strokeText:function(){var t=arguments;this._context.strokeText(t[0],t[1],t[2])},transform:function(){var t=arguments;this._context.transform(t[0],t[1],t[2],t[3],t[4],t[5])},translate:function(){var t=arguments;this._context.translate(t[0],t[1])},_enableTrace:function(){var e,n,i=this,a=t.length,r=Konva.Util._simplifyArray,o=this.setAttr;for(e=0;e255?255:t<0?0:Math.round(t)},alphaComponent:function(t){return t>1?1:t<1e-4?1e-4:t}}}(),function(t){"use strict";var e="Shape",n=["id"],i=["xChange.konva","yChange.konva","scaleXChange.konva","scaleYChange.konva","skewXChange.konva","skewYChange.konva","rotationChange.konva","offsetXChange.konva","offsetYChange.konva","transformsEnabledChange.konva"].join(" "),a=["scaleXChange.konva","scaleYChange.konva"].join(" ");t.Node=function(t){this._init(t)},t.Util.addMethods(t.Node,{_init:function(e){var n=this;this._id=t.idCounter++,this.eventListeners={},this.attrs={},this._cache={},this._filterUpToDate=!1,this._isUnderCache=!1,this.setAttrs(e),this.on(i,function(){this._clearCache("transform"),n._clearSelfAndDescendantCache("absoluteTransform")}),this.on(a,function(){n._clearSelfAndDescendantCache("absoluteScale")}),this.on("visibleChange.konva",function(){n._clearSelfAndDescendantCache("visible")}),this.on("listeningChange.konva",function(){n._clearSelfAndDescendantCache("listening")}),this.on("opacityChange.konva",function(){n._clearSelfAndDescendantCache("absoluteOpacity")})},_clearCache:function(t){t?delete this._cache[t]:this._cache={}},_getCache:function(t,e){return void 0===this._cache[t]&&(this._cache[t]=e.call(this)),this._cache[t]},_clearSelfAndDescendantCache:function(t){this._clearCache(t),this.children&&this.getChildren().each(function(e){e._clearSelfAndDescendantCache(t)})},clearCache:function(){return delete this._cache.canvas,this._filterUpToDate=!1,this},cache:function(e){var n=e||{},i=this.getClientRect(!0),a=n.width||i.width,r=n.height||i.height,o=n.pixelRatio,s=n.x||i.x,h=n.y||i.y,c=n.offset||0,l=n.drawBorder||!1;if(!a||!r)throw new Error("Width or height of caching configuration equals 0.");a+=2*c,r+=2*c,s-=c,h-=c;var d=new t.SceneCanvas({pixelRatio:o,width:a,height:r}),u=new t.SceneCanvas({pixelRatio:o,width:a,height:r}),v=new t.HitCanvas({pixelRatio:1,width:a,height:r}),f=d.getContext(),g=v.getContext();return v.isCache=!0,this.clearCache(),f.save(),g.save(),f.translate(-s,-h),g.translate(-s,-h),this._isUnderCache=!0,this._clearSelfAndDescendantCache("absoluteOpacity"),this._clearSelfAndDescendantCache("absoluteScale"),this.drawScene(d,this,!0),this.drawHit(v,this,!0),this._isUnderCache=!1,f.restore(),g.restore(),l&&(f.save(),f.beginPath(),f.rect(0,0,a,r),f.closePath(),f.setAttr("strokeStyle","red"),f.setAttr("lineWidth",5),f.stroke(),f.restore()),this._cache.canvas={scene:d,filter:u,hit:v,x:s,y:h},this},getClientRect:function(){throw new Error('abstract "getClientRect" method call')},_transformedRect:function(t){var e,n,i,a,r=[{x:t.x,y:t.y},{x:t.x+t.width,y:t.y},{x:t.x+t.width,y:t.y+t.height},{x:t.x,y:t.y+t.height}],o=this.getTransform();return r.forEach(function(t){var r=o.point(t);void 0===e&&(e=i=r.x,n=a=r.y),e=Math.min(e,r.x),n=Math.min(n,r.y),i=Math.max(i,r.x),a=Math.max(a,r.y)}),{x:e,y:n,width:i-e,height:a-n}},_drawCachedSceneCanvas:function(t){t.save(),t._applyOpacity(this),t._applyGlobalCompositeOperation(this),t.translate(this._cache.canvas.x,this._cache.canvas.y);var e=this._getCachedSceneCanvas(),n=e.pixelRatio;t.drawImage(e._canvas,0,0,e.width/n,e.height/n),t.restore()},_drawCachedHitCanvas:function(t){var e=this._cache.canvas,n=e.hit;t.save(),t.translate(this._cache.canvas.x,this._cache.canvas.y),t.drawImage(n._canvas,0,0),t.restore()},_getCachedSceneCanvas:function(){var e,n,i,a,r=this.filters(),o=this._cache.canvas,s=o.scene,h=o.filter,c=h.getContext();if(r){if(!this._filterUpToDate){var l=s.pixelRatio;try{for(e=r.length,c.clear(),c.drawImage(s._canvas,0,0,s.getWidth()/l,s.getHeight()/l),n=c.getImageData(0,0,h.getWidth(),h.getHeight()),i=0;i0&&n[0].getDepth()<=o&&t(n)}var n,i,a,r,o=this.getDepth(),s=this,h=0;return"Stage"!==s.nodeType&&t(s.getStage().getChildren()),h},getDepth:function(){for(var t=0,e=this.parent;e;)t++,e=e.parent;return t},setPosition:function(t){return this.setX(t.x),this.setY(t.y),this},getPosition:function(){return{x:this.getX(),y:this.getY()}},getAbsolutePosition:function(e){var n=this.getAbsoluteTransform(e).getMatrix(),i=new t.Transform,a=this.offset();return i.m=n.slice(),i.translate(a.x,a.y),i.getTranslation()},setAbsolutePosition:function(t){var e,n=this._clearTransform();return this.attrs.x=n.x,this.attrs.y=n.y,delete n.x,delete n.y,e=this.getAbsoluteTransform(),e.invert(),e.translate(t.x,t.y),t={x:this.attrs.x+e.getTranslation().x,y:this.attrs.y+e.getTranslation().y},this.setPosition({x:t.x,y:t.y}),this._setTransform(n),this},_setTransform:function(t){var e;for(e in t)this.attrs[e]=t[e];this._clearCache("transform"),this._clearSelfAndDescendantCache("absoluteTransform")},_clearTransform:function(){var t={x:this.getX(),y:this.getY(),rotation:this.getRotation(),scaleX:this.getScaleX(),scaleY:this.getScaleY(),offsetX:this.getOffsetX(),offsetY:this.getOffsetY(),skewX:this.getSkewX(),skewY:this.getSkewY()};return this.attrs.x=0,this.attrs.y=0,this.attrs.rotation=0,this.attrs.scaleX=1,this.attrs.scaleY=1,this.attrs.offsetX=0,this.attrs.offsetY=0,this.attrs.skewX=0,this.attrs.skewY=0,this._clearCache("transform"),this._clearSelfAndDescendantCache("absoluteTransform"),t},move:function(t){var e=t.x,n=t.y,i=this.getX(),a=this.getY();return void 0!==e&&(i+=e),void 0!==n&&(a+=n),this.setPosition({x:i,y:a}),this},_eachAncestorReverse:function(t,e){var n,i,a=[],r=this.getParent();if(e&&e._id===this._id)return t(this),!0;for(a.unshift(this);r&&(!e||r._id!==e._id);)a.unshift(r),r=r.parent;for(n=a.length,i=0;i0&&(this.parent.children.splice(e,1),this.parent.children.splice(e-1,0,this),this.parent._setChildrenIndices(),!0)},moveToBottom:function(){if(!this.parent)return t.Util.warn("Node has no parent. moveToBottom function is ignored."),!1;var e=this.index;return e>0&&(this.parent.children.splice(e,1),this.parent.children.unshift(this),this.parent._setChildrenIndices(),!0)},setZIndex:function(e){if(!this.parent)return t.Util.warn("Node has no parent. zIndex parameter is ignored."),!1;var n=this.index;return this.parent.children.splice(n,1),this.parent.children.splice(e,0,this),this.parent._setChildrenIndices(),this},getAbsoluteOpacity:function(){return this._getCache("absoluteOpacity",this._getAbsoluteOpacity)},_getAbsoluteOpacity:function(){var t=this.getOpacity(),e=this.getParent();return e&&!e._isUnderCache&&(t*=this.getParent().getAbsoluteOpacity()),t},moveTo:function(t){return this.getParent()!==t&&((this.__originalRemove||this.remove).call(this),t.add(this)),this},toObject:function(){var e,n,i,a,r={},o=this.getAttrs();r.attrs={};for(e in o)n=o[e],i=this[e],delete o[e],a=i?i.call(this):null,o[e]=n,a!==n&&(r.attrs[e]=n);return r.className=this.getClassName(),t.Util._prepareToStringify(r)},toJSON:function(){return JSON.stringify(this.toObject())},getParent:function(){return this.parent},findAncestors:function(t,e,n){var i=[];e&&this._isMatch(t)&&i.push(this);for(var a=this.parent;a;){if(a===n)return i;a._isMatch(t)&&i.push(a),a=a.parent}return i},findAncestor:function(t,e,n){return this.findAncestors(t,e,n)[0]},_isMatch:function(e){if(!e)return!1;var n,i,a=e.replace(/ /g,"").split(","),r=a.length;for(n=0;n>W,0!==T?(T=255/T,A[l]=(u*H>>W)*T,A[l+1]=(v*H>>W)*T,A[l+2]=(f*H>>W)*T):A[l]=A[l+1]=A[l+2]=0,u-=p,v-=m,f-=_,g-=y,p-=E.r,m-=E.g,_-=E.b,y-=E.a,h=d+((h=r+a+1)>W,T>0?(T=255/T,A[h]=(u*H>>W)*T,A[h+1]=(v*H>>W)*T,A[h+2]=(f*H>>W)*T):A[h]=A[h+1]=A[h+2]=0,u-=p,v-=m,f-=_,g-=y,p-=E.r,m-=E.g,_-=E.b,y-=E.a,h=r+((h=o+L)0&&e(t,n)},Konva.Factory.addGetterSetter(Konva.Node,"blurRadius",0,null,Konva.Factory.afterSetFilter)}(),function(){"use strict";function t(t,e,n){var i=4*(n*t.width+e),a=[];return a.push(t.data[i++],t.data[i++],t.data[i++],t.data[i++]),a}function e(t,e){return Math.sqrt(Math.pow(t[0]-e[0],2)+Math.pow(t[1]-e[1],2)+Math.pow(t[2]-e[2],2))}function n(t){for(var e=[0,0,0],n=0;n=0&&v=0&&f=0&&v=0&&f=1020?255:0}return o}function s(t,e,n){for(var i=[1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9,1/9],a=Math.round(Math.sqrt(i.length)),r=Math.floor(a/2),o=[],s=0;s=0&&v=0&&f255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"green",0,function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"blue",0,Konva.Validators.RGBComponent,Konva.Factory.afterSetFilter)}(),function(){"use strict";Konva.Filters.RGBA=function(t){var e,n,i=t.data,a=i.length,r=this.red(),o=this.green(),s=this.blue(),h=this.alpha();for(e=0;e255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"green",0,function(t){return this._filterUpToDate=!1,t>255?255:t<0?0:Math.round(t)}),Konva.Factory.addGetterSetter(Konva.Node,"blue",0,Konva.Validators.RGBComponent,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"alpha",1,function(t){return this._filterUpToDate=!1,t>1?1:t<0?0:t})}(),function(){"use strict";Konva.Filters.HSV=function(t){var e,n,i,a,r,o=t.data,s=o.length,h=Math.pow(2,this.value()),c=Math.pow(2,this.saturation()),l=Math.abs(this.hue()+360)%360,d=h*c*Math.cos(l*Math.PI/180),u=h*c*Math.sin(l*Math.PI/180),v=.299*h+.701*d+.167*u,f=.587*h-.587*d+.33*u,g=.114*h-.114*d-.497*u,p=.299*h-.299*d-.328*u,m=.587*h+.413*d+.035*u,_=.114*h-.114*d+.293*u,y=.299*h-.3*d+1.25*u,K=.587*h-.586*d-1.05*u,S=.114*h+.886*d-.2*u;for(e=0;ec&&(v=0);var f=(d-1+v)*h*4,g=h;do{var p=u+4*(g-1),m=o;g+m<1&&(m=0),g+m>h&&(m=0);var _=f+4*(g-1+m),y=s[p]-s[_],K=s[p+1]-s[_+1],S=s[p+2]-s[_+2],C=y,x=C>0?C:-C,w=K>0?K:-K,b=S>0?S:-S;if(w>x&&(C=K),b>x&&(C=S),C*=e,a){var F=s[p]+C,T=s[p+1]+C,P=s[p+2]+C;s[p]=F>255?255:F<0?0:F,s[p+1]=T>255?255:T<0?0:T,s[p+2]=P>255?255:P<0?0:P}else{var A=n-C;A<0?A=0:A>255&&(A=255),s[p]=s[p+1]=s[p+2]=A}}while(--g)}while(--d)},Konva.Factory.addGetterSetter(Konva.Node,"embossStrength",.5,null,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"embossWhiteLevel",.5,null,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"embossDirection","top-left",null,Konva.Factory.afterSetFilter),Konva.Factory.addGetterSetter(Konva.Node,"embossBlend",!1,null,Konva.Factory.afterSetFilter)}(),function(){"use strict";function t(t,e,n,i,a){var r,o=n-e,s=a-i;return 0===o?i+s/2:0===s?i:(r=(t-e)/o,r=s*r+i)}Konva.Filters.Enhance=function(e){var n,i,a,r,o=e.data,s=o.length,h=o[0],c=h,l=o[1],d=l,u=o[2],v=u,f=this.enhance();if(0!==f){for(r=0;rc&&(c=n),i=o[r+1],id&&(d=i),a=o[r+2],av&&(v=a);c===h&&(c=255,h=0),d===l&&(d=255,l=0),v===u&&(v=255,u=0);var g,p,m,_,y,K,S,C,x;for(f>0?(p=c+f*(255-c),m=h-f*(h-0),y=d+f*(255-d),K=l-f*(l-0),C=v+f*(255-v),x=u-f*(u-0)):(g=.5*(c+h),p=c+f*(c-g),m=h+f*(h-g),_=.5*(d+l),y=d+f*(d-_),K=l+f*(l-_),S=.5*(v+u),C=v+f*(v-S),x=u+f*(u-S)),r=0;r=p))for(n=l;n=m||(i=4*(p*n+e),a+=t[i+0],r+=t[i+1],o+=t[i+2],s+=t[i+3],f+=1);for(a/=f,r/=f,o/=f,e=h;e=p))for(n=l;n=m||(i=4*(p*n+e),t[i+0]=a,t[i+1]=r,t[i+2]=o,t[i+3]=s)}},Konva.Factory.addGetterSetter(Konva.Node,"pixelSize",8,null,Konva.Factory.afterSetFilter)}(),function(){"use strict";Konva.Filters.Threshold=function(t){var e,n=255*this.threshold(),i=t.data,a=i.length;for(e=0;e255?255:s,l[i+1]=h>255?255:h,l[i+2]=c>255?255:c,l[i+3]=l[i+3]}while(--n)}while(--u)}}(),function(){"use strict";Konva.Filters.Solarize=function(t){var e=t.data,n=t.width,i=t.height,a=4*n,r=i;do{var o=(r-1)*a,s=n;do{var h=o+4*(s-1),c=e[h],l=e[h+1],d=e[h+2];c>127&&(c=255-c),l>127&&(l=255-l),d>127&&(d=255-d),e[h]=c,e[h+1]=l,e[h+2]=d}while(--s)}while(--r)}}(),function(){"use strict";var t=function(t,e,n){var i,a,r,o,s=t.data,h=e.data,c=t.width,l=t.height,d=n.polarCenterX||c/2,u=n.polarCenterY||l/2,v=0,f=0,g=0,p=0,m=Math.sqrt(d*d+u*u);a=c-d,r=l-u,o=Math.sqrt(a*a+r*r),m=o>m?o:m;var _,y,K,S,x=l,C=c,w=360/C*Math.PI/180;for(y=0;yy?h:y;var K,S,x,C,w=u,b=d,F=n.polarRotation||0;for(a=0;af&&(x=S,C=0,w=-1),r=0;r0},removeChildren:function(){for(var t,e=Konva.Collection.toCollection(this.children),n=0;n1){for(var e=0;e0},destroy:function(){return t.Node.prototype.destroy.call(this),delete t.shapes[this.colorKey],this},_useBufferCanvas:function(t){return!t&&this.perfectDrawEnabled()&&1!==this.getAbsoluteOpacity()&&this.hasFill()&&this.hasStroke()&&this.getStage()||this.perfectDrawEnabled()&&this.hasShadow()&&1!==this.getAbsoluteOpacity()&&this.hasFill()&&this.hasStroke()&&this.getStage()},getSelfRect:function(){var t=this.getSize();return{x:this._centroid?Math.round(-t.width/2):0,y:this._centroid?Math.round(-t.height/2):0,width:t.width,height:t.height}},getClientRect:function(t){var e=this.getSelfRect(),n=this.hasStroke()&&this.strokeWidth()||0,i=e.width+n,a=e.height+n,r=this.hasShadow()?this.shadowOffsetX():0,o=this.hasShadow()?this.shadowOffsetY():0,s=i+Math.abs(r),h=a+Math.abs(o),c=this.hasShadow()&&this.shadowBlur()||0,l=s+2*c,d=h+2*c,u=0;Math.round(n/2)!==n/2&&(u=1);var v={width:l+u,height:d+u,x:-Math.round(n/2+c)+Math.min(r,0)+e.x,y:-Math.round(n/2+c)+Math.min(o,0)+e.y};return t?v:this._transformedRect(v)},drawScene:function(t,e,n,i){var a,r,o,s=this.getLayer(),h=t||s.getCanvas(),c=h.getContext(),l=this._cache.canvas,d=this.sceneFunc(),u=this.hasShadow(),v=this.hasStroke();if(!this.isVisible())return this;if(l)return c.save(),s._applyTransform(this,c,e),this._drawCachedSceneCanvas(c),c.restore(),this;if(!d)return this;if(c.save(),this._useBufferCanvas(n)&&!i){if(a=this.getStage(),r=a.bufferCanvas,o=r.getContext(),o.clear(),o.save(),o._applyLineJoin(this),!n)if(s)s._applyTransform(this,o,e);else{var f=this.getAbsoluteTransform(e).getMatrix();c.transform(f[0],f[1],f[2],f[3],f[4],f[5])}d.call(this,o),o.restore();var g=r.pixelRatio;u&&!h.hitCanvas?(c.save(),c._applyShadow(this),c._applyOpacity(this),c.drawImage(r._canvas,0,0,r.width/g,r.height/g),c.restore()):(c._applyOpacity(this),c.drawImage(r._canvas,0,0,r.width/g,r.height/g))}else{if(c._applyLineJoin(this),!n)if(s)s._applyTransform(this,c,e);else{var p=this.getAbsoluteTransform(e).getMatrix();c.transform(p[0],p[1],p[2],p[3],p[4],p[5])}u&&v&&!h.hitCanvas?(c.save(),n||c._applyOpacity(this),c._applyShadow(this),d.call(this,c),c.restore(),this.hasFill()&&this.getShadowForStrokeEnabled()&&d.call(this,c)):u&&!h.hitCanvas?(c.save(),n||c._applyOpacity(this),c._applyShadow(this),d.call(this,c),c.restore()):(n||c._applyOpacity(this),d.call(this,c))}return c.restore(),this},drawHit:function(t,e,n){var i=this.getLayer(),a=t||i.hitCanvas,r=a.getContext(),o=this.hitFunc()||this.sceneFunc(),s=this._cache.canvas,h=s&&s.hit;if(!this.shouldDrawHit(a))return this;if(i&&i.clearHitCache(),h)return r.save(),i._applyTransform(this,r,e),this._drawCachedHitCanvas(r),r.restore(),this;if(!o)return this;if(r.save(),r._applyLineJoin(this),!n)if(i)i._applyTransform(this,r,e);else{var c=this.getAbsoluteTransform(e).getMatrix();r.transform(c[0],c[1],c[2],c[3],c[4],c[5])}return o.call(this,r),r.restore(),this},drawHitFromCache:function(e){var n,i,a,r,o,s,h=e||0,c=this._cache.canvas,l=this._getCachedSceneCanvas(),d=c.hit,u=d.getContext(),v=d.getWidth(),f=d.getHeight();u.clear(),u.drawImage(l._canvas,0,0,v,f);try{for(n=u.getImageData(0,0,v,f),i=n.data,a=i.length,r=t.Util._hexToRgb(this.colorKey),o=0;oh?(i[o]=r.r,i[o+1]=r.g,i[o+2]=r.b,i[o+3]=255):i[o+3]=0;u.putImageData(n,0,0)}catch(e){t.Util.error("Unable to draw hit graph from cached scene canvas. "+e.message)}return this}}),t.Util.extend(t.Shape,t.Node),t.Factory.addGetterSetter(t.Shape,"stroke"),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeRed",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeGreen",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeBlue",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeAlpha",1,t.Validators.alphaComponent),t.Factory.addGetterSetter(t.Shape,"strokeWidth",2),t.Factory.addGetterSetter(t.Shape,"strokeHitEnabled",!0),t.Factory.addGetterSetter(t.Shape,"perfectDrawEnabled",!0),t.Factory.addGetterSetter(t.Shape,"shadowForStrokeEnabled",!0),t.Factory.addGetterSetter(t.Shape,"lineJoin"),t.Factory.addGetterSetter(t.Shape,"lineCap"),t.Factory.addGetterSetter(t.Shape,"sceneFunc"),t.Factory.addGetterSetter(t.Shape,"hitFunc"),t.Factory.addGetterSetter(t.Shape,"dash"),t.Factory.addGetterSetter(t.Shape,"shadowColor"),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowRed",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowGreen",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowBlue",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowAlpha",1,t.Validators.alphaComponent),t.Factory.addGetterSetter(t.Shape,"shadowBlur"),t.Factory.addGetterSetter(t.Shape,"shadowOpacity"),t.Factory.addComponentsGetterSetter(t.Shape,"shadowOffset",["x","y"]),t.Factory.addGetterSetter(t.Shape,"shadowOffsetX",0),t.Factory.addGetterSetter(t.Shape,"shadowOffsetY",0),t.Factory.addGetterSetter(t.Shape,"fillPatternImage"),t.Factory.addGetterSetter(t.Shape,"fill"),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillRed",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillGreen",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillBlue",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillAlpha",1,t.Validators.alphaComponent),t.Factory.addGetterSetter(t.Shape,"fillPatternX",0),t.Factory.addGetterSetter(t.Shape,"fillPatternY",0),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientColorStops"),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientStartRadius",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientEndRadius",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientColorStops"),t.Factory.addGetterSetter(t.Shape,"fillPatternRepeat","repeat"),t.Factory.addGetterSetter(t.Shape,"fillEnabled",!0),t.Factory.addGetterSetter(t.Shape,"strokeEnabled",!0),t.Factory.addGetterSetter(t.Shape,"shadowEnabled",!0),t.Factory.addGetterSetter(t.Shape,"dashEnabled",!0),t.Factory.addGetterSetter(t.Shape,"strokeScaleEnabled",!0),t.Factory.addGetterSetter(t.Shape,"fillPriority","color"),t.Factory.addComponentsGetterSetter(t.Shape,"fillPatternOffset",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillPatternOffsetX",0),t.Factory.addGetterSetter(t.Shape,"fillPatternOffsetY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillPatternScale",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillPatternScaleX",1),t.Factory.addGetterSetter(t.Shape,"fillPatternScaleY",1),t.Factory.addComponentsGetterSetter(t.Shape,"fillLinearGradientStartPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientStartPointX",0),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientStartPointY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillLinearGradientEndPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientEndPointX",0),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientEndPointY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillRadialGradientStartPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientStartPointX",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientStartPointY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillRadialGradientEndPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientEndPointX",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientEndPointY",0),t.Factory.addGetterSetter(t.Shape,"fillPatternRotation",0),t.Factory.backCompat(t.Shape,{dashArray:"dash",getDashArray:"getDash",setDashArray:"getDash",drawFunc:"sceneFunc",getDrawFunc:"getSceneFunc",setDrawFunc:"setSceneFunc",drawHitFunc:"hitFunc",getDrawHitFunc:"getHitFunc",setDrawHitFunc:"setHitFunc"}),t.Collection.mapMethods(t.Shape)}(Konva),function(){"use strict";function t(t,n){t.content.addEventListener(n,function(i){t[e+n](i)},!1)}var e="_",n=["mousedown","mousemove","mouseup","mouseout","touchstart","touchmove","touchend","mouseover","DOMMouseScroll","mousewheel","wheel","contextmenu"],i=n.length;Konva.Stage=function(t){this.___init(t)},Konva.Util.addMethods(Konva.Stage,{___init:function(t){this.nodeType="Stage",Konva.Container.call(this,t),this._id=Konva.idCounter++,this._buildDOM(),this._bindContentEvents(),this._enableNestedTransforms=!1,Konva.stages.push(this)},_validateAdd:function(t){"Layer"!==t.getType()&&Konva.Util.throw("You may only add layers to the stage.")},setContainer:function(t){if("string"==typeof t){if("."===t.charAt(0)){var e=t.slice(1);t=Konva.document.getElementsByClassName(e)[0]}else{var n;n="#"!==t.charAt(0)?t:t.slice(1),t=Konva.document.getElementById(n)}if(!t)throw"Can not find container in document with id "+n}return this._setAttr("container",t),this},shouldDrawHit:function(){return!0},draw:function(){return Konva.Node.prototype.draw.call(this),this},setHeight:function(t){return Konva.Node.prototype.setHeight.call(this,t),this._resizeDOM(),this},setWidth:function(t){return Konva.Node.prototype.setWidth.call(this,t),this._resizeDOM(),this},clear:function(){var t,e=this.children,n=e.length;for(t=0;t-1&&Konva.stages.splice(e,1),this},getPointerPosition:function(){return this.pointerPos},getStage:function(){return this},getContent:function(){return this.content},toDataURL:function(t){t=t||{};var e=t.mimeType||null,n=t.quality||null,i=t.x||0,a=t.y||0,r=new Konva.SceneCanvas({width:t.width||this.getWidth(),height:t.height||this.getHeight(),pixelRatio:t.pixelRatio}),o=r.getContext()._context,s=this.children;(i||a)&&o.translate(-1*i,-1*a),s.each(function(t){var e=t.getCanvas().getWidth(),n=t.getCanvas().getHeight(),i=t.getCanvas().getPixelRatio();o.drawImage(t.getCanvas()._canvas,0,0,e/i,n/i)});var h=r.toDataURL(e,n);return t.callback&&t.callback(h),h},toImage:function(t){var e=t.callback;t.callback=function(t){Konva.Util._getImage(t,function(t){e(t)})},this.toDataURL(t)},getIntersection:function(t,e){var n,i,a=this.getChildren(),r=a.length,o=r-1;for(n=o;n>=0;n--)if(i=a[n].getIntersection(t,e))return i;return null},_resizeDOM:function(){if(this.content){var t,e,n=this.getWidth(),i=this.getHeight(),a=this.getChildren(),r=a.length;for(this.content.style.width=n+"px",this.content.style.height=i+"px",this.bufferCanvas.setSize(n,i),this.bufferHitCanvas.setSize(n,i),t=0;t1){for(var e=0;e0){var a=t.touches[0];n=a.clientX-e.left,i=a.clientY-e.top}}else n=t.clientX-e.left,i=t.clientY-e.top;null!==n&&null!==i&&(this.pointerPos={x:n,y:i})},_getContentPosition:function(){var t=this.content.getBoundingClientRect?this.content.getBoundingClientRect():{top:0,left:0};return{top:t.top,left:t.left}},_buildDOM:function(){var t=this.getContainer();if(!t){if(Konva.Util.isBrowser())throw"Stage has no container. A container is required.";t=Konva.document.createElement("div")}t.innerHTML="",this.content=Konva.document.createElement("div"),this.content.style.position="relative",this.content.className="konvajs-content",this.content.setAttribute("role","presentation"),t.appendChild(this.content),this.bufferCanvas=new Konva.SceneCanvas,this.bufferHitCanvas=new Konva.HitCanvas({pixelRatio:1}),this._resizeDOM()},_onContent:function(t,e){var n,i,a=t.split(" "),r=a.length;for(n=0;n0?{antialiased:!0}:{}},drawScene:function(t,e){var n=this.getLayer(),i=t||n&&n.getCanvas();return this._fire("beforeDraw",{node:this}),this.getClearBeforeDraw()&&i.getContext().clear(),Konva.Container.prototype.drawScene.call(this,i,e),this._fire("draw",{node:this}),this},drawHit:function(t,e){var n=this.getLayer(),i=t||n&&n.hitCanvas;return n&&n.getClearBeforeDraw()&&n.getHitCanvas().getContext().clear(),Konva.Container.prototype.drawHit.call(this,i,e),this.imageData=null,this},clear:function(t){return Konva.BaseLayer.prototype.clear.call(this,t),this.getHitCanvas().getContext().clear(t),this.imageData=null,this},setVisible:function(t){return Konva.Node.prototype.setVisible.call(this,t),t?(this.getCanvas()._canvas.style.display="block",this.hitCanvas._canvas.style.display="block"):(this.getCanvas()._canvas.style.display="none",this.hitCanvas._canvas.style.display="none"),this},enableHitGraph:function(){return this.setHitGraphEnabled(!0),this},disableHitGraph:function(){return this.setHitGraphEnabled(!1),this},setSize:function(t,e){return Konva.BaseLayer.prototype.setSize.call(this,t,e),this.hitCanvas.setSize(t,e),this}}),Konva.Util.extend(Konva.Layer,Konva.BaseLayer),Konva.Factory.addGetterSetter(Konva.Layer,"hitGraphEnabled",!0), -Konva.Collection.mapMethods(Konva.Layer)}(),function(){"use strict";Konva.FastLayer=function(t){this.____init(t)},Konva.Util.addMethods(Konva.FastLayer,{____init:function(t){this.nodeType="Layer",this.canvas=new Konva.SceneCanvas,Konva.BaseLayer.call(this,t)},_validateAdd:function(t){"Shape"!==t.getType()&&Konva.Util.throw("You may only add shapes to a fast layer.")},_setCanvasSize:function(t,e){this.canvas.setSize(t,e)},hitGraphEnabled:function(){return!1},getIntersection:function(){return null},drawScene:function(t){var e=this.getLayer(),n=t||e&&e.getCanvas();return this.getClearBeforeDraw()&&n.getContext().clear(),Konva.Container.prototype.drawScene.call(this,n),this},draw:function(){return this.drawScene(),this},setVisible:function(t){return Konva.Node.prototype.setVisible.call(this,t),this.getCanvas()._canvas.style.display=t?"block":"none",this}}),Konva.Util.extend(Konva.FastLayer,Konva.BaseLayer),Konva.Collection.mapMethods(Konva.FastLayer)}(),function(){"use strict";Konva.Group=function(t){this.___init(t)},Konva.Util.addMethods(Konva.Group,{___init:function(t){this.nodeType="Group",Konva.Container.call(this,t)},_validateAdd:function(t){var e=t.getType();"Group"!==e&&"Shape"!==e&&Konva.Util.throw("You may only add groups and shapes to groups.")}}),Konva.Util.extend(Konva.Group,Konva.Container),Konva.Collection.mapMethods(Konva.Group)}(),function(t){"use strict";function e(t){setTimeout(t,1e3/60)}function n(){return a.apply(t.global,arguments)}var i=function(){return t.global.performance&&t.global.performance.now?function(){return t.global.performance.now()}:function(){return(new Date).getTime()}}(),a=function(){return t.global.requestAnimationFrame||t.global.webkitRequestAnimationFrame||t.global.mozRequestAnimationFrame||t.global.oRequestAnimationFrame||t.global.msRequestAnimationFrame||e}();t.Animation=function(e,n){var a=t.Animation;this.func=e,this.setLayers(n),this.id=a.animIdCounter++,this.frame={time:0,timeDiff:0,lastTime:i()}},t.Animation.prototype={setLayers:function(t){var e=[];return e=t?t.length>0?t:[t]:[],this.layers=e,this},getLayers:function(){return this.layers},addLayer:function(t){var e,n=this.layers,i=n.length;for(e=0;ethis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())},getTime:function(){return this._time},setPosition:function(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t},getPosition:function(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)},play:function(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},reverse:function(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},seek:function(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")},reset:function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},finish:function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},update:function(){this.setPosition(this.getPosition(this._time))},onEnterFrame:function(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)},pause:function(){this.state=1,this.fire("onPause")},getTimer:function(){return(new Date).getTime()}},Konva.Tween=function(n){var a,r,o=this,s=n.node,h=s._id,c=n.easing||Konva.Easings.Linear,l=!!n.yoyo;a=void 0===n.duration?1:0===n.duration?.001:n.duration,this.node=s,this._id=e++;var d=s.getLayer()||(s instanceof Konva.Stage?s.getLayers():null);d||Konva.Util.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new Konva.Animation(function(){o.tween.onEnterFrame()},d),this.tween=new i(r,function(t){o._tweenFunc(t)},c,0,1,1e3*a,l),this._addListeners(),Konva.Tween.attrs[h]||(Konva.Tween.attrs[h]={}),Konva.Tween.attrs[h][this._id]||(Konva.Tween.attrs[h][this._id]={}),Konva.Tween.tweens[h]||(Konva.Tween.tweens[h]={});for(r in n)void 0===t[r]&&this._addAttr(r,n[r]);this.reset(),this.onFinish=n.onFinish,this.onReset=n.onReset},Konva.Tween.attrs={},Konva.Tween.tweens={},Konva.Tween.prototype={_addAttr:function(t,e){var i,a,r,o,s,h,c,l=this.node,d=l._id;if(r=Konva.Tween.tweens[d][t],r&&delete Konva.Tween.attrs[d][r][t],i=l.getAttr(t),Konva.Util._isArray(e))for(a=[],s=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(c=i,i=Konva.Util._prepareArrayForTween(i,e,l.closed())):(h=e,e=Konva.Util._prepareArrayForTween(e,i,l.closed()))),o=0;ol)for(;_.length>0;){for(var K=0,S=_.length,x="",C=0;K>>1,b=_.slice(0,w+1),F=this._getTextWidth(b);F<=l?(K=w+1,x=b,C=F):S=w}if(!x)break;if(g){var T=Math.max(x.lastIndexOf(" "),x.lastIndexOf("-"))+1;T>0&&(K=T,x=x.slice(0,K),C=this._getTextWidth(x))}if(this._addTextLine(x),n=Math.max(n,C),u+=i,!f||h&&u+i>d)break;if(_=_.slice(K),_.length>0&&(y=this._getTextWidth(_))<=l){this._addTextLine(_),u+=i,n=Math.max(n,y);break}}else this._addTextLine(_),u+=i,n=Math.max(n,y);if(h&&u+i>d)break}a.restore(),this.textHeight=e,this.textWidth=n}},Konva.Util.extend(Konva.Text,Konva.Shape),Konva.Factory.addGetterSetter(Konva.Text,"fontFamily","Arial"),Konva.Factory.addGetterSetter(Konva.Text,"fontSize",12),Konva.Factory.addGetterSetter(Konva.Text,"fontStyle","normal"),Konva.Factory.addGetterSetter(Konva.Text,"fontVariant","normal"),Konva.Factory.addGetterSetter(Konva.Text,"padding",0),Konva.Factory.addGetterSetter(Konva.Text,"align","left"),Konva.Factory.addGetterSetter(Konva.Text,"lineHeight",1),Konva.Factory.addGetterSetter(Konva.Text,"wrap","word"),Konva.Factory.addGetterSetter(Konva.Text,"letterSpacing",0),Konva.Factory.addGetter(Konva.Text,"text",""),Konva.Factory.addOverloadedGetterSetter(Konva.Text,"text"),Konva.Factory.addGetterSetter(Konva.Text,"textDecoration",""),Konva.Collection.mapMethods(Konva.Text)}(),function(){"use strict";Konva.Line=function(t){this.___init(t)},Konva.Line.prototype={___init:function(t){Konva.Shape.call(this,t),this.className="Line",this.on("pointsChange.konva tensionChange.konva closedChange.konva",function(){this._clearCache("tensionPoints")}),this.sceneFunc(this._sceneFunc)},_sceneFunc:function(t){var e,n,i,a=this.getPoints(),r=a.length,o=this.getTension(),s=this.getClosed();if(r){if(t.beginPath(),t.moveTo(a[0],a[1]),0!==o&&r>4){for(e=this.getTensionPoints(),n=e.length,i=s?0:4,s||t.quadraticCurveTo(e[0],e[1],e[2],e[3]);ih?s:h,f=s>h?1:s/h,g=s>h?h/s:1;t.translate(r,o),t.rotate(d),t.scale(f,g),t.arc(0,0,v,c,c+l,1-u),t.scale(1/f,1/g),t.rotate(-d),t.translate(-r,-o);break;case"z":t.closePath()}}t.fillStrokeShape(this)},getSelfRect:function(){var t=[];this.dataArray.forEach(function(e){t=t.concat(e.points)});for(var e,n,i=t[0],a=t[0],r=t[1],o=t[1],s=0;s0&&""===l[0]&&l.shift();for(var d=0;d0&&!isNaN(l[0]);){var u,v,f,g,p,m,_,y,K,S,x=null,C=[],w=o,b=s;switch(c){case"l":o+=l.shift(),s+=l.shift(),x="L",C.push(o,s);break;case"L":o=l.shift(),s=l.shift(),C.push(o,s);break;case"m":var F=l.shift(),T=l.shift();if(o+=F,s+=T,x="M",r.length>2&&"z"===r[r.length-1].command)for(var P=r.length-2;P>=0;P--)if("M"===r[P].command){o=r[P].points[0]+F,s=r[P].points[1]+T;break}C.push(o,s),c="l";break;case"M":o=l.shift(),s=l.shift(),x="M",C.push(o,s),c="L";break;case"h":o+=l.shift(),x="L",C.push(o,s);break;case"H":o=l.shift(),x="L",C.push(o,s);break;case"v":s+=l.shift(),x="L",C.push(o,s);break;case"V":s=l.shift(),x="L",C.push(o,s);break;case"C":C.push(l.shift(),l.shift(),l.shift(),l.shift()),o=l.shift(),s=l.shift(),C.push(o,s);break;case"c":C.push(o+l.shift(),s+l.shift(),o+l.shift(),s+l.shift()),o+=l.shift(),s+=l.shift(),x="C",C.push(o,s);break;case"S":v=o,f=s,u=r[r.length-1],"C"===u.command&&(v=o+(o-u.points[2]),f=s+(s-u.points[3])),C.push(v,f,l.shift(),l.shift()),o=l.shift(),s=l.shift(),x="C",C.push(o,s);break;case"s":v=o,f=s,u=r[r.length-1],"C"===u.command&&(v=o+(o-u.points[2]),f=s+(s-u.points[3])),C.push(v,f,o+l.shift(),s+l.shift()),o+=l.shift(),s+=l.shift(),x="C",C.push(o,s);break;case"Q":C.push(l.shift(),l.shift()),o=l.shift(),s=l.shift(),C.push(o,s);break;case"q":C.push(o+l.shift(),s+l.shift()),o+=l.shift(),s+=l.shift(),x="Q",C.push(o,s);break;case"T":v=o,f=s,u=r[r.length-1],"Q"===u.command&&(v=o+(o-u.points[0]),f=s+(s-u.points[1])),o=l.shift(),s=l.shift(),x="Q",C.push(v,f,o,s);break;case"t":v=o,f=s,u=r[r.length-1],"Q"===u.command&&(v=o+(o-u.points[0]),f=s+(s-u.points[1])),o+=l.shift(),s+=l.shift(),x="Q",C.push(v,f,o,s);break;case"A":g=l.shift(),p=l.shift(),m=l.shift(),_=l.shift(),y=l.shift(),K=o,S=s,o=l.shift(),s=l.shift(),x="A",C=this.convertEndpointToCenterParameterization(K,S,o,s,_,y,g,p,m);break;case"a":g=l.shift(),p=l.shift(),m=l.shift(),_=l.shift(),y=l.shift(),K=o,S=s,o+=l.shift(),s+=l.shift(),x="A",C=this.convertEndpointToCenterParameterization(K,S,o,s,_,y,g,p,m)}r.push({command:x||c,points:C,start:{x:w,y:b},pathLength:this.calcLength(w,b,x||c,C)})}"z"!==c&&"Z"!==c||r.push({command:"z",points:[],start:void 0,pathLength:0})}return r},Konva.Path.calcLength=function(t,e,n,i){var a,r,o,s,h=Konva.Path;switch(n){case"L":return h.getLineLength(t,e,i[0],i[1]);case"C":for(a=0,r=h.getPointOnCubicBezier(0,t,e,i[0],i[1],i[2],i[3],i[4],i[5]),s=.01;s<=1;s+=.01)o=h.getPointOnCubicBezier(s,t,e,i[0],i[1],i[2],i[3],i[4],i[5]),a+=h.getLineLength(r.x,r.y,o.x,o.y),r=o;return a;case"Q":for(a=0,r=h.getPointOnQuadraticBezier(0,t,e,i[0],i[1],i[2],i[3]),s=.01;s<=1;s+=.01)o=h.getPointOnQuadraticBezier(s,t,e,i[0],i[1],i[2],i[3]),a+=h.getLineLength(r.x,r.y,o.x,o.y),r=o;return a;case"A":a=0;var c=i[4],l=i[5],d=i[4]+l,u=Math.PI/180;if(Math.abs(c-d)d;s-=u)o=h.getPointOnEllipticalArc(i[0],i[1],i[2],i[3],s,0),a+=h.getLineLength(r.x,r.y,o.x,o.y),r=o;else for(s=c+u;s1&&(o*=Math.sqrt(u),s*=Math.sqrt(u));var v=Math.sqrt((o*o*(s*s)-o*o*(d*d)-s*s*(l*l))/(o*o*(d*d)+s*s*(l*l)));a===r&&(v*=-1),isNaN(v)&&(v=0);var f=v*o*d/s,g=v*-s*l/o,p=(t+n)/2+Math.cos(c)*f-Math.sin(c)*g,m=(e+i)/2+Math.sin(c)*f+Math.cos(c)*g,_=function(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])},y=function(t,e){return(t[0]*e[0]+t[1]*e[1])/(_(t)*_(e))},K=function(t,e){return(t[0]*e[1]=1&&(w=0),0===r&&w>0&&(w-=2*Math.PI),1===r&&w<0&&(w+=2*Math.PI),[p,m,o,s,S,w,c,r]},Konva.Factory.addGetterSetter(Konva.Path,"data"),Konva.Collection.mapMethods(Konva.Path)}(),function(){"use strict";function t(t){t.fillText(this.partialText,0,0)}function e(t){t.strokeText(this.partialText,0,0)}Konva.TextPath=function(t){this.___init(t)},Konva.TextPath.prototype={___init:function(n){var i=this;this.dummyCanvas=Konva.Util.createCanvasElement(),this.dataArray=[],Konva.Shape.call(this,n),this._fillFunc=t,this._strokeFunc=e,this._fillFuncHit=t,this._strokeFuncHit=e,this.className="TextPath",this.dataArray=Konva.Path.parsePathData(this.attrs.data),this.kerningTable=this.attrs.kerningTable,this.on("dataChange.konva",function(){i.dataArray=Konva.Path.parsePathData(this.attrs.data),i._setTextData()}),this.on("textChange.konva alignChange.konva letterSpacingChange.konva kerningTableChange.konva",i._setTextData),i._setTextData(),this.sceneFunc(this._sceneFunc),this.hitFunc(this._hitFunc)},_sceneFunc:function(t){t.setAttr("font",this._getContextFont()),t.setAttr("textBaseline",this.getTextBaseline()),t.setAttr("textAlign","left"),t.save();var e=this.textDecoration(),n=this.fill(),i=this.fontSize(),a=this.glyphInfo;"underline"===e&&t.beginPath();for(var r=0;r=1){var n=e[0].p0;t.moveTo(n.x,n.y)}for(var i=0;i0&&(o+=t.dataArray[s].pathLength);var h=0;"center"===a&&(h=Math.max(0,o/2-r/2)),"right"===a&&(h=Math.max(0,o-r));for(var c,l,d,u=this.getText().split(""),v=this.getText().split(" ").length-1,f=-1,g=0,p=function(){g=0;for(var e=t.dataArray,n=f+1;n0)return f=n,e[n];"M"===e[n].command&&(c={x:e[n].points[0],y:e[n].points[1]})}return{}},m=function(e){var i=t._getTextSize(e).width+n;" "===e&&"justify"===a&&(i+=(o-r)/v);var s=0,h=0;for(l=void 0;Math.abs(i-s)/i>.01&&h<25;){h++;for(var u=s;void 0===d;)(d=p())&&u+d.pathLengthi?l=Konva.Path.getPointOnLine(i,c.x,c.y,d.points[0],d.points[1],c.x,c.y):d=void 0;break;case"A":var m=d.points[4],_=d.points[5],y=d.points[4]+_;0===g?g=m+1e-8:i>s?g+=Math.PI/180*_/Math.abs(_):g-=Math.PI/360*_/Math.abs(_),(_<0&&g=0&&g>y)&&(g=y,f=!0),l=Konva.Path.getPointOnEllipticalArc(d.points[0],d.points[1],d.points[2],d.points[3],g,d.points[6]);break;case"C":0===g?g=i>d.pathLength?1e-8:i/d.pathLength:i>s?g+=(i-s)/d.pathLength:g-=(s-i)/d.pathLength,g>1&&(g=1,f=!0),l=Konva.Path.getPointOnCubicBezier(g,d.start.x,d.start.y,d.points[0],d.points[1],d.points[2],d.points[3],d.points[4],d.points[5]);break;case"Q":0===g?g=i/d.pathLength:i>s?g+=(i-s)/d.pathLength:g-=(s-i)/d.pathLength,g>1&&(g=1,f=!0),l=Konva.Path.getPointOnQuadraticBezier(g,d.start.x,d.start.y,d.points[0],d.points[1],d.points[2],d.points[3])}void 0!==l&&(s=Konva.Path.getLineLength(c.x,c.y,l.x,l.y)),f&&(f=!1,d=void 0)}},_=t._getTextSize("C").width+n,y=0;y255?255:s,l[i+1]=h>255?255:h,l[i+2]=c>255?255:c,l[i+3]=l[i+3]}while(--n)}while(--u)}}(),function(){"use strict";Konva.Filters.Solarize=function(t){var e=t.data,n=t.width,i=t.height,a=4*n,r=i;do{var o=(r-1)*a,s=n;do{var h=o+4*(s-1),c=e[h],l=e[h+1],d=e[h+2];c>127&&(c=255-c),l>127&&(l=255-l),d>127&&(d=255-d),e[h]=c,e[h+1]=l,e[h+2]=d}while(--s)}while(--r)}}(),function(){"use strict";var t=function(t,e,n){var i,a,r,o,s=t.data,h=e.data,c=t.width,l=t.height,d=n.polarCenterX||c/2,u=n.polarCenterY||l/2,v=0,f=0,g=0,p=0,m=Math.sqrt(d*d+u*u);a=c-d,r=l-u,o=Math.sqrt(a*a+r*r),m=o>m?o:m;var _,y,K,S,C=l,x=c,w=360/x*Math.PI/180;for(y=0;yy?h:y;var K,S,C,x,w=u,b=d,F=n.polarRotation||0;for(a=0;af&&(C=S,x=0,w=-1),r=0;r0},removeChildren:function(){for(var t,e=Konva.Collection.toCollection(this.children),n=0;n1){for(var e=0;e0},destroy:function(){return t.Node.prototype.destroy.call(this),delete t.shapes[this.colorKey],this},_useBufferCanvas:function(t){return!t&&this.perfectDrawEnabled()&&1!==this.getAbsoluteOpacity()&&this.hasFill()&&this.hasStroke()&&this.getStage()||this.perfectDrawEnabled()&&this.hasShadow()&&1!==this.getAbsoluteOpacity()&&this.hasFill()&&this.hasStroke()&&this.getStage()},getSelfRect:function(){var t=this.getSize();return{x:this._centroid?Math.round(-t.width/2):0,y:this._centroid?Math.round(-t.height/2):0,width:t.width,height:t.height}},getClientRect:function(t){var e=this.getSelfRect(),n=this.hasStroke()&&this.strokeWidth()||0,i=e.width+n,a=e.height+n,r=this.hasShadow()?this.shadowOffsetX():0,o=this.hasShadow()?this.shadowOffsetY():0,s=i+Math.abs(r),h=a+Math.abs(o),c=this.hasShadow()&&this.shadowBlur()||0,l=s+2*c,d=h+2*c,u=0;Math.round(n/2)!==n/2&&(u=1);var v={width:l+u,height:d+u,x:-Math.round(n/2+c)+Math.min(r,0)+e.x,y:-Math.round(n/2+c)+Math.min(o,0)+e.y};return t?v:this._transformedRect(v)},drawScene:function(t,e,n,i){var a,r,o,s=this.getLayer(),h=t||s.getCanvas(),c=h.getContext(),l=this._cache.canvas,d=this.sceneFunc(),u=this.hasShadow(),v=this.hasStroke();if(!this.isVisible())return this;if(l)return c.save(),s._applyTransform(this,c,e),this._drawCachedSceneCanvas(c),c.restore(),this;if(!d)return this;if(c.save(),this._useBufferCanvas(n)&&!i){if(a=this.getStage(),r=a.bufferCanvas,o=r.getContext(),o.clear(),o.save(),o._applyLineJoin(this),!n)if(s)s._applyTransform(this,o,e);else{var f=this.getAbsoluteTransform(e).getMatrix();c.transform(f[0],f[1],f[2],f[3],f[4],f[5])}d.call(this,o),o.restore();var g=r.pixelRatio;u&&!h.hitCanvas?(c.save(),c._applyShadow(this),c._applyOpacity(this),c._applyGlobalCompositeOperation(this),c.drawImage(r._canvas,0,0,r.width/g,r.height/g),c.restore()):(c._applyOpacity(this),c._applyGlobalCompositeOperation(this),c.drawImage(r._canvas,0,0,r.width/g,r.height/g))}else{if(c._applyLineJoin(this),!n)if(s)s._applyTransform(this,c,e);else{var p=this.getAbsoluteTransform(e).getMatrix();c.transform(p[0],p[1],p[2],p[3],p[4],p[5])}u&&v&&!h.hitCanvas?(c.save(),n||(c._applyOpacity(this),c._applyGlobalCompositeOperation(this)),c._applyShadow(this),d.call(this,c),c.restore(),this.hasFill()&&this.getShadowForStrokeEnabled()&&d.call(this,c)):u&&!h.hitCanvas?(c.save(),n||(c._applyOpacity(this),c._applyGlobalCompositeOperation(this)),c._applyShadow(this),d.call(this,c),c.restore()):(n||(c._applyOpacity(this),c._applyGlobalCompositeOperation(this)),d.call(this,c))}return c.restore(),this},drawHit:function(t,e,n){var i=this.getLayer(),a=t||i.hitCanvas,r=a.getContext(),o=this.hitFunc()||this.sceneFunc(),s=this._cache.canvas,h=s&&s.hit;if(!this.shouldDrawHit(a))return this;if(i&&i.clearHitCache(),h)return r.save(),i._applyTransform(this,r,e),this._drawCachedHitCanvas(r),r.restore(),this;if(!o)return this;if(r.save(),r._applyLineJoin(this),!n)if(i)i._applyTransform(this,r,e);else{var c=this.getAbsoluteTransform(e).getMatrix();r.transform(c[0],c[1],c[2],c[3],c[4],c[5])}return o.call(this,r),r.restore(),this},drawHitFromCache:function(e){var n,i,a,r,o,s,h=e||0,c=this._cache.canvas,l=this._getCachedSceneCanvas(),d=c.hit,u=d.getContext(),v=d.getWidth(),f=d.getHeight();u.clear(),u.drawImage(l._canvas,0,0,v,f);try{for(n=u.getImageData(0,0,v,f),i=n.data,a=i.length,r=t.Util._hexToRgb(this.colorKey),o=0;oh?(i[o]=r.r,i[o+1]=r.g,i[o+2]=r.b,i[o+3]=255):i[o+3]=0;u.putImageData(n,0,0)}catch(e){t.Util.error("Unable to draw hit graph from cached scene canvas. "+e.message)}return this}}),t.Util.extend(t.Shape,t.Node),t.Factory.addGetterSetter(t.Shape,"stroke"),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeRed",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeGreen",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeBlue",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"strokeAlpha",1,t.Validators.alphaComponent),t.Factory.addGetterSetter(t.Shape,"strokeWidth",2),t.Factory.addGetterSetter(t.Shape,"strokeHitEnabled",!0),t.Factory.addGetterSetter(t.Shape,"perfectDrawEnabled",!0),t.Factory.addGetterSetter(t.Shape,"shadowForStrokeEnabled",!0),t.Factory.addGetterSetter(t.Shape,"lineJoin"),t.Factory.addGetterSetter(t.Shape,"lineCap"),t.Factory.addGetterSetter(t.Shape,"sceneFunc"),t.Factory.addGetterSetter(t.Shape,"hitFunc"),t.Factory.addGetterSetter(t.Shape,"dash"),t.Factory.addGetterSetter(t.Shape,"dashOffset",0),t.Factory.addGetterSetter(t.Shape,"shadowColor"),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowRed",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowGreen",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowBlue",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"shadowAlpha",1,t.Validators.alphaComponent),t.Factory.addGetterSetter(t.Shape,"shadowBlur"),t.Factory.addGetterSetter(t.Shape,"shadowOpacity"),t.Factory.addComponentsGetterSetter(t.Shape,"shadowOffset",["x","y"]),t.Factory.addGetterSetter(t.Shape,"shadowOffsetX",0),t.Factory.addGetterSetter(t.Shape,"shadowOffsetY",0),t.Factory.addGetterSetter(t.Shape,"fillPatternImage"),t.Factory.addGetterSetter(t.Shape,"fill"),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillRed",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillGreen",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillBlue",0,t.Validators.RGBComponent),t.Factory.addDeprecatedGetterSetter(t.Shape,"fillAlpha",1,t.Validators.alphaComponent),t.Factory.addGetterSetter(t.Shape,"fillPatternX",0),t.Factory.addGetterSetter(t.Shape,"fillPatternY",0),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientColorStops"),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientStartRadius",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientEndRadius",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientColorStops"),t.Factory.addGetterSetter(t.Shape,"fillPatternRepeat","repeat"),t.Factory.addGetterSetter(t.Shape,"fillEnabled",!0),t.Factory.addGetterSetter(t.Shape,"strokeEnabled",!0),t.Factory.addGetterSetter(t.Shape,"shadowEnabled",!0),t.Factory.addGetterSetter(t.Shape,"dashEnabled",!0),t.Factory.addGetterSetter(t.Shape,"strokeScaleEnabled",!0),t.Factory.addGetterSetter(t.Shape,"fillPriority","color"),t.Factory.addComponentsGetterSetter(t.Shape,"fillPatternOffset",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillPatternOffsetX",0),t.Factory.addGetterSetter(t.Shape,"fillPatternOffsetY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillPatternScale",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillPatternScaleX",1),t.Factory.addGetterSetter(t.Shape,"fillPatternScaleY",1),t.Factory.addComponentsGetterSetter(t.Shape,"fillLinearGradientStartPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientStartPointX",0),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientStartPointY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillLinearGradientEndPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientEndPointX",0),t.Factory.addGetterSetter(t.Shape,"fillLinearGradientEndPointY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillRadialGradientStartPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientStartPointX",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientStartPointY",0),t.Factory.addComponentsGetterSetter(t.Shape,"fillRadialGradientEndPoint",["x","y"]),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientEndPointX",0),t.Factory.addGetterSetter(t.Shape,"fillRadialGradientEndPointY",0),t.Factory.addGetterSetter(t.Shape,"fillPatternRotation",0),t.Factory.backCompat(t.Shape,{dashArray:"dash",getDashArray:"getDash",setDashArray:"getDash",drawFunc:"sceneFunc",getDrawFunc:"getSceneFunc",setDrawFunc:"setSceneFunc",drawHitFunc:"hitFunc",getDrawHitFunc:"getHitFunc",setDrawHitFunc:"setHitFunc"}),t.Collection.mapMethods(t.Shape)}(Konva),function(){"use strict";function t(t,n){t.content.addEventListener(n,function(i){t[e+n](i)},!1)}var e="_",n=["mousedown","mousemove","mouseup","mouseout","touchstart","touchmove","touchend","mouseover","DOMMouseScroll","mousewheel","wheel","contextmenu"],i=n.length;Konva.Stage=function(t){this.___init(t)},Konva.Util.addMethods(Konva.Stage,{___init:function(t){this.nodeType="Stage",Konva.Container.call(this,t),this._id=Konva.idCounter++,this._buildDOM(),this._bindContentEvents(),this._enableNestedTransforms=!1,Konva.stages.push(this)},_validateAdd:function(t){"Layer"!==t.getType()&&Konva.Util.throw("You may only add layers to the stage.")},setContainer:function(t){if("string"==typeof t){if("."===t.charAt(0)){var e=t.slice(1);t=Konva.document.getElementsByClassName(e)[0]}else{var n;n="#"!==t.charAt(0)?t:t.slice(1),t=Konva.document.getElementById(n)}if(!t)throw"Can not find container in document with id "+n}return this._setAttr("container",t),this},shouldDrawHit:function(){return!0},draw:function(){return Konva.Node.prototype.draw.call(this),this},setHeight:function(t){return Konva.Node.prototype.setHeight.call(this,t),this._resizeDOM(),this},setWidth:function(t){return Konva.Node.prototype.setWidth.call(this,t),this._resizeDOM(),this},clear:function(){var t,e=this.children,n=e.length;for(t=0;t-1&&Konva.stages.splice(e,1),this},getPointerPosition:function(){return this.pointerPos},getStage:function(){return this},getContent:function(){return this.content},toDataURL:function(t){t=t||{};var e=t.mimeType||null,n=t.quality||null,i=t.x||0,a=t.y||0,r=new Konva.SceneCanvas({width:t.width||this.getWidth(),height:t.height||this.getHeight(),pixelRatio:t.pixelRatio}),o=r.getContext()._context,s=this.children;(i||a)&&o.translate(-1*i,-1*a),s.each(function(t){var e=t.getCanvas().getWidth(),n=t.getCanvas().getHeight(),i=t.getCanvas().getPixelRatio();o.drawImage(t.getCanvas()._canvas,0,0,e/i,n/i)});var h=r.toDataURL(e,n);return t.callback&&t.callback(h),h},toImage:function(t){var e=t.callback;t.callback=function(t){Konva.Util._getImage(t,function(t){e(t)})},this.toDataURL(t)},getIntersection:function(t,e){var n,i,a=this.getChildren(),r=a.length,o=r-1;for(n=o;n>=0;n--)if(i=a[n].getIntersection(t,e))return i;return null},_resizeDOM:function(){if(this.content){var t,e,n=this.getWidth(),i=this.getHeight(),a=this.getChildren(),r=a.length;for(this.content.style.width=n+"px",this.content.style.height=i+"px",this.bufferCanvas.setSize(n,i),this.bufferHitCanvas.setSize(n,i),t=0;t1){for(var e=0;e0){var a=t.touches[0];n=a.clientX-e.left,i=a.clientY-e.top}}else n=t.clientX-e.left,i=t.clientY-e.top;null!==n&&null!==i&&(this.pointerPos={x:n,y:i})},_getContentPosition:function(){var t=this.content.getBoundingClientRect?this.content.getBoundingClientRect():{top:0,left:0};return{top:t.top,left:t.left}},_buildDOM:function(){var t=this.getContainer();if(!t){if(Konva.Util.isBrowser())throw"Stage has no container. A container is required.";t=Konva.document.createElement("div")}t.innerHTML="",this.content=Konva.document.createElement("div"),this.content.style.position="relative",this.content.className="konvajs-content",this.content.setAttribute("role","presentation"),t.appendChild(this.content),this.bufferCanvas=new Konva.SceneCanvas,this.bufferHitCanvas=new Konva.HitCanvas({pixelRatio:1}),this._resizeDOM()},_onContent:function(t,e){var n,i,a=t.split(" "),r=a.length;for(n=0;n0?{antialiased:!0}:{}},drawScene:function(t,e){var n=this.getLayer(),i=t||n&&n.getCanvas();return this._fire("beforeDraw",{node:this}),this.getClearBeforeDraw()&&i.getContext().clear(),Konva.Container.prototype.drawScene.call(this,i,e),this._fire("draw",{node:this}),this},drawHit:function(t,e){var n=this.getLayer(),i=t||n&&n.hitCanvas;return n&&n.getClearBeforeDraw()&&n.getHitCanvas().getContext().clear(),Konva.Container.prototype.drawHit.call(this,i,e),this.imageData=null,this},clear:function(t){return Konva.BaseLayer.prototype.clear.call(this,t),this.getHitCanvas().getContext().clear(t),this.imageData=null,this},setVisible:function(t){return Konva.Node.prototype.setVisible.call(this,t),t?(this.getCanvas()._canvas.style.display="block",this.hitCanvas._canvas.style.display="block"):(this.getCanvas()._canvas.style.display="none",this.hitCanvas._canvas.style.display="none"),this},enableHitGraph:function(){return this.setHitGraphEnabled(!0),this},disableHitGraph:function(){return this.setHitGraphEnabled(!1),this}, +setSize:function(t,e){return Konva.BaseLayer.prototype.setSize.call(this,t,e),this.hitCanvas.setSize(t,e),this}}),Konva.Util.extend(Konva.Layer,Konva.BaseLayer),Konva.Factory.addGetterSetter(Konva.Layer,"hitGraphEnabled",!0),Konva.Collection.mapMethods(Konva.Layer)}(),function(){"use strict";Konva.FastLayer=function(t){this.____init(t)},Konva.Util.addMethods(Konva.FastLayer,{____init:function(t){this.nodeType="Layer",this.canvas=new Konva.SceneCanvas,Konva.BaseLayer.call(this,t)},_validateAdd:function(t){"Shape"!==t.getType()&&Konva.Util.throw("You may only add shapes to a fast layer.")},_setCanvasSize:function(t,e){this.canvas.setSize(t,e)},hitGraphEnabled:function(){return!1},getIntersection:function(){return null},drawScene:function(t){var e=this.getLayer(),n=t||e&&e.getCanvas();return this.getClearBeforeDraw()&&n.getContext().clear(),Konva.Container.prototype.drawScene.call(this,n),this},draw:function(){return this.drawScene(),this},setVisible:function(t){return Konva.Node.prototype.setVisible.call(this,t),this.getCanvas()._canvas.style.display=t?"block":"none",this}}),Konva.Util.extend(Konva.FastLayer,Konva.BaseLayer),Konva.Collection.mapMethods(Konva.FastLayer)}(),function(){"use strict";Konva.Group=function(t){this.___init(t)},Konva.Util.addMethods(Konva.Group,{___init:function(t){this.nodeType="Group",Konva.Container.call(this,t)},_validateAdd:function(t){var e=t.getType();"Group"!==e&&"Shape"!==e&&Konva.Util.throw("You may only add groups and shapes to groups.")}}),Konva.Util.extend(Konva.Group,Konva.Container),Konva.Collection.mapMethods(Konva.Group)}(),function(t){"use strict";function e(t){setTimeout(t,1e3/60)}function n(){return a.apply(t.global,arguments)}var i=function(){return t.global.performance&&t.global.performance.now?function(){return t.global.performance.now()}:function(){return(new Date).getTime()}}(),a=function(){return t.global.requestAnimationFrame||t.global.webkitRequestAnimationFrame||t.global.mozRequestAnimationFrame||t.global.oRequestAnimationFrame||t.global.msRequestAnimationFrame||e}();t.Animation=function(e,n){var a=t.Animation;this.func=e,this.setLayers(n),this.id=a.animIdCounter++,this.frame={time:0,timeDiff:0,lastTime:i()}},t.Animation.prototype={setLayers:function(t){var e=[];return e=t?t.length>0?t:[t]:[],this.layers=e,this},getLayers:function(){return this.layers},addLayer:function(t){var e,n=this.layers,i=n.length;for(e=0;ethis.duration?this.yoyo?(this._time=this.duration,this.reverse()):this.finish():t<0?this.yoyo?(this._time=0,this.play()):this.reset():(this._time=t,this.update())},getTime:function(){return this._time},setPosition:function(t){this.prevPos=this._pos,this.propFunc(t),this._pos=t},getPosition:function(t){return void 0===t&&(t=this._time),this.func(t,this.begin,this._change,this.duration)},play:function(){this.state=2,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onPlay")},reverse:function(){this.state=3,this._time=this.duration-this._time,this._startTime=this.getTimer()-this._time,this.onEnterFrame(),this.fire("onReverse")},seek:function(t){this.pause(),this._time=t,this.update(),this.fire("onSeek")},reset:function(){this.pause(),this._time=0,this.update(),this.fire("onReset")},finish:function(){this.pause(),this._time=this.duration,this.update(),this.fire("onFinish")},update:function(){this.setPosition(this.getPosition(this._time))},onEnterFrame:function(){var t=this.getTimer()-this._startTime;2===this.state?this.setTime(t):3===this.state&&this.setTime(this.duration-t)},pause:function(){this.state=1,this.fire("onPause")},getTimer:function(){return(new Date).getTime()}},Konva.Tween=function(n){var a,r,o=this,s=n.node,h=s._id,c=n.easing||Konva.Easings.Linear,l=!!n.yoyo;a=void 0===n.duration?1:0===n.duration?.001:n.duration,this.node=s,this._id=e++;var d=s.getLayer()||(s instanceof Konva.Stage?s.getLayers():null);d||Konva.Util.error("Tween constructor have `node` that is not in a layer. Please add node into layer first."),this.anim=new Konva.Animation(function(){o.tween.onEnterFrame()},d),this.tween=new i(r,function(t){o._tweenFunc(t)},c,0,1,1e3*a,l),this._addListeners(),Konva.Tween.attrs[h]||(Konva.Tween.attrs[h]={}),Konva.Tween.attrs[h][this._id]||(Konva.Tween.attrs[h][this._id]={}),Konva.Tween.tweens[h]||(Konva.Tween.tweens[h]={});for(r in n)void 0===t[r]&&this._addAttr(r,n[r]);this.reset(),this.onFinish=n.onFinish,this.onReset=n.onReset},Konva.Tween.attrs={},Konva.Tween.tweens={},Konva.Tween.prototype={_addAttr:function(t,e){var i,a,r,o,s,h,c,l=this.node,d=l._id;if(r=Konva.Tween.tweens[d][t],r&&delete Konva.Tween.attrs[d][r][t],i=l.getAttr(t),Konva.Util._isArray(e))for(a=[],s=Math.max(e.length,i.length),"points"===t&&e.length!==i.length&&(e.length>i.length?(c=i,i=Konva.Util._prepareArrayForTween(i,e,l.closed())):(h=e,e=Konva.Util._prepareArrayForTween(e,i,l.closed()))),o=0;ol)for(;_.length>0;){for(var K=0,S=_.length,C="",x=0;K>>1,b=_.slice(0,w+1),F=this._getTextWidth(b);F<=l?(K=w+1,C=b,x=F):S=w}if(!C)break;if(g){var T=Math.max(C.lastIndexOf(" "),C.lastIndexOf("-"))+1;T>0&&(K=T,C=C.slice(0,K),x=this._getTextWidth(C))}if(this._addTextLine(C),n=Math.max(n,x),u+=i,!f||h&&u+i>d)break;if(_=_.slice(K),_.length>0&&(y=this._getTextWidth(_))<=l){this._addTextLine(_),u+=i,n=Math.max(n,y);break}}else this._addTextLine(_),u+=i,n=Math.max(n,y);if(h&&u+i>d)break}a.restore(),this.textHeight=e,this.textWidth=n}},Konva.Util.extend(Konva.Text,Konva.Shape),Konva.Factory.addGetterSetter(Konva.Text,"fontFamily","Arial"),Konva.Factory.addGetterSetter(Konva.Text,"fontSize",12),Konva.Factory.addGetterSetter(Konva.Text,"fontStyle","normal"),Konva.Factory.addGetterSetter(Konva.Text,"fontVariant","normal"),Konva.Factory.addGetterSetter(Konva.Text,"padding",0),Konva.Factory.addGetterSetter(Konva.Text,"align","left"),Konva.Factory.addGetterSetter(Konva.Text,"lineHeight",1),Konva.Factory.addGetterSetter(Konva.Text,"wrap","word"),Konva.Factory.addGetterSetter(Konva.Text,"letterSpacing",0),Konva.Factory.addGetter(Konva.Text,"text",""),Konva.Factory.addOverloadedGetterSetter(Konva.Text,"text"),Konva.Factory.addGetterSetter(Konva.Text,"textDecoration",""),Konva.Collection.mapMethods(Konva.Text)}(),function(){"use strict";Konva.Line=function(t){this.___init(t)},Konva.Line.prototype={___init:function(t){Konva.Shape.call(this,t),this.className="Line",this.on("pointsChange.konva tensionChange.konva closedChange.konva",function(){this._clearCache("tensionPoints")}),this.sceneFunc(this._sceneFunc)},_sceneFunc:function(t){var e,n,i,a=this.getPoints(),r=a.length,o=this.getTension(),s=this.getClosed();if(r){if(t.beginPath(),t.moveTo(a[0],a[1]),0!==o&&r>4){for(e=this.getTensionPoints(),n=e.length,i=s?0:4,s||t.quadraticCurveTo(e[0],e[1],e[2],e[3]);ih?s:h,f=s>h?1:s/h,g=s>h?h/s:1;t.translate(r,o),t.rotate(d),t.scale(f,g),t.arc(0,0,v,c,c+l,1-u),t.scale(1/f,1/g),t.rotate(-d),t.translate(-r,-o);break;case"z":t.closePath()}}t.fillStrokeShape(this)},getSelfRect:function(){var t=[];this.dataArray.forEach(function(e){t=t.concat(e.points)});for(var e,n,i=t[0],a=t[0],r=t[1],o=t[1],s=0;s0&&""===l[0]&&l.shift();for(var d=0;d0&&!isNaN(l[0]);){var u,v,f,g,p,m,_,y,K,S,C=null,x=[],w=o,b=s;switch(c){case"l":o+=l.shift(),s+=l.shift(),C="L",x.push(o,s);break;case"L":o=l.shift(),s=l.shift(),x.push(o,s);break;case"m":var F=l.shift(),T=l.shift();if(o+=F,s+=T,C="M",r.length>2&&"z"===r[r.length-1].command)for(var P=r.length-2;P>=0;P--)if("M"===r[P].command){o=r[P].points[0]+F,s=r[P].points[1]+T;break}x.push(o,s),c="l";break;case"M":o=l.shift(),s=l.shift(),C="M",x.push(o,s),c="L";break;case"h":o+=l.shift(),C="L",x.push(o,s);break;case"H":o=l.shift(),C="L",x.push(o,s);break;case"v":s+=l.shift(),C="L",x.push(o,s);break;case"V":s=l.shift(),C="L",x.push(o,s);break;case"C":x.push(l.shift(),l.shift(),l.shift(),l.shift()),o=l.shift(),s=l.shift(),x.push(o,s);break;case"c":x.push(o+l.shift(),s+l.shift(),o+l.shift(),s+l.shift()),o+=l.shift(),s+=l.shift(),C="C",x.push(o,s);break;case"S":v=o,f=s,u=r[r.length-1],"C"===u.command&&(v=o+(o-u.points[2]),f=s+(s-u.points[3])),x.push(v,f,l.shift(),l.shift()),o=l.shift(),s=l.shift(),C="C",x.push(o,s);break;case"s":v=o,f=s,u=r[r.length-1],"C"===u.command&&(v=o+(o-u.points[2]),f=s+(s-u.points[3])),x.push(v,f,o+l.shift(),s+l.shift()),o+=l.shift(),s+=l.shift(),C="C",x.push(o,s);break;case"Q":x.push(l.shift(),l.shift()),o=l.shift(),s=l.shift(),x.push(o,s);break;case"q":x.push(o+l.shift(),s+l.shift()),o+=l.shift(),s+=l.shift(),C="Q",x.push(o,s);break;case"T":v=o,f=s,u=r[r.length-1],"Q"===u.command&&(v=o+(o-u.points[0]),f=s+(s-u.points[1])),o=l.shift(),s=l.shift(),C="Q",x.push(v,f,o,s);break;case"t":v=o,f=s,u=r[r.length-1],"Q"===u.command&&(v=o+(o-u.points[0]),f=s+(s-u.points[1])),o+=l.shift(),s+=l.shift(),C="Q",x.push(v,f,o,s);break;case"A":g=l.shift(),p=l.shift(),m=l.shift(),_=l.shift(),y=l.shift(),K=o,S=s,o=l.shift(),s=l.shift(),C="A",x=this.convertEndpointToCenterParameterization(K,S,o,s,_,y,g,p,m);break;case"a":g=l.shift(),p=l.shift(),m=l.shift(),_=l.shift(),y=l.shift(),K=o,S=s,o+=l.shift(),s+=l.shift(),C="A",x=this.convertEndpointToCenterParameterization(K,S,o,s,_,y,g,p,m)}r.push({command:C||c,points:x,start:{x:w,y:b},pathLength:this.calcLength(w,b,C||c,x)})}"z"!==c&&"Z"!==c||r.push({command:"z",points:[],start:void 0,pathLength:0})}return r},Konva.Path.calcLength=function(t,e,n,i){var a,r,o,s,h=Konva.Path;switch(n){case"L":return h.getLineLength(t,e,i[0],i[1]);case"C":for(a=0,r=h.getPointOnCubicBezier(0,t,e,i[0],i[1],i[2],i[3],i[4],i[5]),s=.01;s<=1;s+=.01)o=h.getPointOnCubicBezier(s,t,e,i[0],i[1],i[2],i[3],i[4],i[5]),a+=h.getLineLength(r.x,r.y,o.x,o.y),r=o;return a;case"Q":for(a=0,r=h.getPointOnQuadraticBezier(0,t,e,i[0],i[1],i[2],i[3]),s=.01;s<=1;s+=.01)o=h.getPointOnQuadraticBezier(s,t,e,i[0],i[1],i[2],i[3]),a+=h.getLineLength(r.x,r.y,o.x,o.y),r=o;return a;case"A":a=0;var c=i[4],l=i[5],d=i[4]+l,u=Math.PI/180;if(Math.abs(c-d)d;s-=u)o=h.getPointOnEllipticalArc(i[0],i[1],i[2],i[3],s,0),a+=h.getLineLength(r.x,r.y,o.x,o.y),r=o;else for(s=c+u;s1&&(o*=Math.sqrt(u),s*=Math.sqrt(u));var v=Math.sqrt((o*o*(s*s)-o*o*(d*d)-s*s*(l*l))/(o*o*(d*d)+s*s*(l*l)));a===r&&(v*=-1),isNaN(v)&&(v=0);var f=v*o*d/s,g=v*-s*l/o,p=(t+n)/2+Math.cos(c)*f-Math.sin(c)*g,m=(e+i)/2+Math.sin(c)*f+Math.cos(c)*g,_=function(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])},y=function(t,e){return(t[0]*e[0]+t[1]*e[1])/(_(t)*_(e))},K=function(t,e){return(t[0]*e[1]=1&&(w=0),0===r&&w>0&&(w-=2*Math.PI),1===r&&w<0&&(w+=2*Math.PI),[p,m,o,s,S,w,c,r]},Konva.Factory.addGetterSetter(Konva.Path,"data"),Konva.Collection.mapMethods(Konva.Path)}(),function(){"use strict";function t(t){t.fillText(this.partialText,0,0)}function e(t){t.strokeText(this.partialText,0,0)}Konva.TextPath=function(t){this.___init(t)},Konva.TextPath.prototype={___init:function(n){var i=this;this.dummyCanvas=Konva.Util.createCanvasElement(),this.dataArray=[],Konva.Shape.call(this,n),this._fillFunc=t,this._strokeFunc=e,this._fillFuncHit=t,this._strokeFuncHit=e,this.className="TextPath",this.dataArray=Konva.Path.parsePathData(this.attrs.data),this.kerningTable=this.attrs.kerningTable,this.on("dataChange.konva",function(){i.dataArray=Konva.Path.parsePathData(this.attrs.data),i._setTextData()}),this.on("textChange.konva alignChange.konva letterSpacingChange.konva kerningTableChange.konva",i._setTextData),i._setTextData(),this.sceneFunc(this._sceneFunc),this.hitFunc(this._hitFunc)},_sceneFunc:function(t){t.setAttr("font",this._getContextFont()),t.setAttr("textBaseline",this.getTextBaseline()),t.setAttr("textAlign","left"),t.save();var e=this.textDecoration(),n=this.fill(),i=this.fontSize(),a=this.glyphInfo;"underline"===e&&t.beginPath();for(var r=0;r=1){var n=e[0].p0;t.moveTo(n.x,n.y)}for(var i=0;i0&&(o+=t.dataArray[s].pathLength);var h=0;"center"===a&&(h=Math.max(0,o/2-r/2)),"right"===a&&(h=Math.max(0,o-r));for(var c,l,d,u=this.getText().split(""),v=this.getText().split(" ").length-1,f=-1,g=0,p=function(){g=0;for(var e=t.dataArray,n=f+1;n0)return f=n,e[n];"M"===e[n].command&&(c={x:e[n].points[0],y:e[n].points[1]})}return{}},m=function(e){var i=t._getTextSize(e).width+n;" "===e&&"justify"===a&&(i+=(o-r)/v);var s=0,h=0;for(l=void 0;Math.abs(i-s)/i>.01&&h<25;){h++;for(var u=s;void 0===d;)(d=p())&&u+d.pathLengthi?l=Konva.Path.getPointOnLine(i,c.x,c.y,d.points[0],d.points[1],c.x,c.y):d=void 0;break;case"A":var m=d.points[4],_=d.points[5],y=d.points[4]+_;0===g?g=m+1e-8:i>s?g+=Math.PI/180*_/Math.abs(_):g-=Math.PI/360*_/Math.abs(_),(_<0&&g=0&&g>y)&&(g=y,f=!0),l=Konva.Path.getPointOnEllipticalArc(d.points[0],d.points[1],d.points[2],d.points[3],g,d.points[6]);break;case"C":0===g?g=i>d.pathLength?1e-8:i/d.pathLength:i>s?g+=(i-s)/d.pathLength:g-=(s-i)/d.pathLength,g>1&&(g=1,f=!0),l=Konva.Path.getPointOnCubicBezier(g,d.start.x,d.start.y,d.points[0],d.points[1],d.points[2],d.points[3],d.points[4],d.points[5]);break;case"Q":0===g?g=i/d.pathLength:i>s?g+=(i-s)/d.pathLength:g-=(s-i)/d.pathLength,g>1&&(g=1,f=!0),l=Konva.Path.getPointOnQuadraticBezier(g,d.start.x,d.start.y,d.points[0],d.points[1],d.points[2],d.points[3])}void 0!==l&&(s=Konva.Path.getLineLength(c.x,c.y,l.x,l.y)),f&&(f=!1,d=void 0)}},_=t._getTextSize("C").width+n,y=0;y= Konva.traceArrMax) { - traceArr.shift(); - } - }, - /** + if (len >= Konva.traceArrMax) { + traceArr.shift(); + } + }, + /** * reset canvas context transform * @method * @memberof Konva.Context.prototype */ - reset: function() { - var pixelRatio = this.getCanvas().getPixelRatio(); - this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); - }, - /** + reset: function() { + var pixelRatio = this.getCanvas().getPixelRatio(); + this.setTransform(1 * pixelRatio, 0, 0, 1 * pixelRatio, 0, 0); + }, + /** * get canvas * @method * @memberof Konva.Context.prototype * @returns {Konva.Canvas} */ - getCanvas: function() { - return this.canvas; - }, - /** + getCanvas: function() { + return this.canvas; + }, + /** * clear canvas * @method * @memberof Konva.Context.prototype @@ -199,426 +214,467 @@ * @param {Number} [bounds.width] * @param {Number} [bounds.height] */ - clear: function(bounds) { - var canvas = this.getCanvas(); + clear: function(bounds) { + var canvas = this.getCanvas(); - if (bounds) { - this.clearRect(bounds.x || 0, bounds.y || 0, bounds.width || 0, bounds.height || 0); - } - else { - this.clearRect(0, 0, canvas.getWidth() / canvas.pixelRatio, canvas.getHeight() / canvas.pixelRatio); - } - }, - _applyLineCap: function(shape) { - var lineCap = shape.getLineCap(); - if(lineCap) { - this.setAttr('lineCap', lineCap); - } - }, - _applyOpacity: function(shape) { - var absOpacity = shape.getAbsoluteOpacity(); - if (absOpacity !== 1) { - this.setAttr('globalAlpha', absOpacity); - } - }, - _applyLineJoin: function(shape) { - var lineJoin = shape.getLineJoin(); - if(lineJoin) { - this.setAttr('lineJoin', lineJoin); - } - }, - setAttr: function(attr, val) { - this._context[attr] = val; - }, + if (bounds) { + this.clearRect( + bounds.x || 0, + bounds.y || 0, + bounds.width || 0, + bounds.height || 0 + ); + } else { + this.clearRect( + 0, + 0, + canvas.getWidth() / canvas.pixelRatio, + canvas.getHeight() / canvas.pixelRatio + ); + } + }, + _applyLineCap: function(shape) { + var lineCap = shape.getLineCap(); + if (lineCap) { + this.setAttr('lineCap', lineCap); + } + }, + _applyOpacity: function(shape) { + var absOpacity = shape.getAbsoluteOpacity(); + if (absOpacity !== 1) { + this.setAttr('globalAlpha', absOpacity); + } + }, + _applyLineJoin: function(shape) { + var lineJoin = shape.getLineJoin(); + if (lineJoin) { + this.setAttr('lineJoin', lineJoin); + } + }, + setAttr: function(attr, val) { + this._context[attr] = val; + }, - // context pass through methods - arc: function() { - var a = arguments; - this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - beginPath: function() { - this._context.beginPath(); - }, - bezierCurveTo: function() { - var a = arguments; - this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - clearRect: function() { - var a = arguments; - this._context.clearRect(a[0], a[1], a[2], a[3]); - }, - clip: function() { - this._context.clip(); - }, - closePath: function() { - this._context.closePath(); - }, - createImageData: function() { - var a = arguments; - if(a.length === 2) { - return this._context.createImageData(a[0], a[1]); - } - else if(a.length === 1) { - return this._context.createImageData(a[0]); - } - }, - createLinearGradient: function() { - var a = arguments; - return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); - }, - createPattern: function() { - var a = arguments; - return this._context.createPattern(a[0], a[1]); - }, - createRadialGradient: function() { - var a = arguments; - return this._context.createRadialGradient(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - drawImage: function() { - var a = arguments, - _context = this._context; + // context pass through methods + arc: function() { + var a = arguments; + this._context.arc(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + beginPath: function() { + this._context.beginPath(); + }, + bezierCurveTo: function() { + var a = arguments; + this._context.bezierCurveTo(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + clearRect: function() { + var a = arguments; + this._context.clearRect(a[0], a[1], a[2], a[3]); + }, + clip: function() { + this._context.clip(); + }, + closePath: function() { + this._context.closePath(); + }, + createImageData: function() { + var a = arguments; + if (a.length === 2) { + return this._context.createImageData(a[0], a[1]); + } else if (a.length === 1) { + return this._context.createImageData(a[0]); + } + }, + createLinearGradient: function() { + var a = arguments; + return this._context.createLinearGradient(a[0], a[1], a[2], a[3]); + }, + createPattern: function() { + var a = arguments; + return this._context.createPattern(a[0], a[1]); + }, + createRadialGradient: function() { + var a = arguments; + return this._context.createRadialGradient( + a[0], + a[1], + a[2], + a[3], + a[4], + a[5] + ); + }, + drawImage: function() { + var a = arguments, _context = this._context; - if(a.length === 3) { - _context.drawImage(a[0], a[1], a[2]); - } - else if(a.length === 5) { - _context.drawImage(a[0], a[1], a[2], a[3], a[4]); - } - else if(a.length === 9) { - _context.drawImage(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); - } - }, - isPointInPath: function(x, y) { - return this._context.isPointInPath(x, y); - }, - fill: function() { - this._context.fill(); - }, - fillRect: function(x, y, width, height) { - this._context.fillRect(x, y, width, height); - }, - strokeRect: function(x, y, width, height) { - this._context.strokeRect(x, y, width, height); - }, - fillText: function() { - var a = arguments; - this._context.fillText(a[0], a[1], a[2]); - }, - measureText: function(text) { - return this._context.measureText(text); - }, - getImageData: function() { - var a = arguments; - return this._context.getImageData(a[0], a[1], a[2], a[3]); - }, - lineTo: function() { - var a = arguments; - this._context.lineTo(a[0], a[1]); - }, - moveTo: function() { - var a = arguments; - this._context.moveTo(a[0], a[1]); - }, - rect: function() { - var a = arguments; - this._context.rect(a[0], a[1], a[2], a[3]); - }, - putImageData: function() { - var a = arguments; - this._context.putImageData(a[0], a[1], a[2]); - }, - quadraticCurveTo: function() { - var a = arguments; - this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); - }, - restore: function() { - this._context.restore(); - }, - rotate: function() { - var a = arguments; - this._context.rotate(a[0]); - }, - save: function() { - this._context.save(); - }, - scale: function() { - var a = arguments; - this._context.scale(a[0], a[1]); - }, - setLineDash: function() { - var a = arguments, - _context = this._context; + if (a.length === 3) { + _context.drawImage(a[0], a[1], a[2]); + } else if (a.length === 5) { + _context.drawImage(a[0], a[1], a[2], a[3], a[4]); + } else if (a.length === 9) { + _context.drawImage( + a[0], + a[1], + a[2], + a[3], + a[4], + a[5], + a[6], + a[7], + a[8] + ); + } + }, + isPointInPath: function(x, y) { + return this._context.isPointInPath(x, y); + }, + fill: function() { + this._context.fill(); + }, + fillRect: function(x, y, width, height) { + this._context.fillRect(x, y, width, height); + }, + strokeRect: function(x, y, width, height) { + this._context.strokeRect(x, y, width, height); + }, + fillText: function() { + var a = arguments; + this._context.fillText(a[0], a[1], a[2]); + }, + measureText: function(text) { + return this._context.measureText(text); + }, + getImageData: function() { + var a = arguments; + return this._context.getImageData(a[0], a[1], a[2], a[3]); + }, + lineTo: function() { + var a = arguments; + this._context.lineTo(a[0], a[1]); + }, + moveTo: function() { + var a = arguments; + this._context.moveTo(a[0], a[1]); + }, + rect: function() { + var a = arguments; + this._context.rect(a[0], a[1], a[2], a[3]); + }, + putImageData: function() { + var a = arguments; + this._context.putImageData(a[0], a[1], a[2]); + }, + quadraticCurveTo: function() { + var a = arguments; + this._context.quadraticCurveTo(a[0], a[1], a[2], a[3]); + }, + restore: function() { + this._context.restore(); + }, + rotate: function() { + var a = arguments; + this._context.rotate(a[0]); + }, + save: function() { + this._context.save(); + }, + scale: function() { + var a = arguments; + this._context.scale(a[0], a[1]); + }, + setLineDash: function() { + var a = arguments, _context = this._context; - // works for Chrome and IE11 - if(this._context.setLineDash) { - _context.setLineDash(a[0]); - } - // verified that this works in firefox - else if('mozDash' in _context) { - _context.mozDash = a[0]; - } - // does not currently work for Safari - else if('webkitLineDash' in _context) { - _context.webkitLineDash = a[0]; - } + // works for Chrome and IE11 + if (this._context.setLineDash) { + _context.setLineDash(a[0]); + } else if ('mozDash' in _context) { + // verified that this works in firefox + _context.mozDash = a[0]; + } else if ('webkitLineDash' in _context) { + // does not currently work for Safari + _context.webkitLineDash = a[0]; + } - // no support for IE9 and IE10 - }, - getLineDash: function() { - return this._context.getLineDash(); - }, - setTransform: function() { - var a = arguments; - this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - stroke: function() { - this._context.stroke(); - }, - strokeText: function() { - var a = arguments; - this._context.strokeText(a[0], a[1], a[2]); - }, - transform: function() { - var a = arguments; - this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); - }, - translate: function() { - var a = arguments; - this._context.translate(a[0], a[1]); - }, - _enableTrace: function() { - var that = this, - len = CONTEXT_METHODS.length, - _simplifyArray = Konva.Util._simplifyArray, - origSetter = this.setAttr, - n, args; + // no support for IE9 and IE10 + }, + getLineDash: function() { + return this._context.getLineDash(); + }, + setTransform: function() { + var a = arguments; + this._context.setTransform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + stroke: function() { + this._context.stroke(); + }, + strokeText: function() { + var a = arguments; + this._context.strokeText(a[0], a[1], a[2]); + }, + transform: function() { + var a = arguments; + this._context.transform(a[0], a[1], a[2], a[3], a[4], a[5]); + }, + translate: function() { + var a = arguments; + this._context.translate(a[0], a[1]); + }, + _enableTrace: function() { + var that = this, + len = CONTEXT_METHODS.length, + _simplifyArray = Konva.Util._simplifyArray, + origSetter = this.setAttr, + n, + args; - // to prevent creating scope function at each loop - var func = function(methodName) { - var origMethod = that[methodName], - ret; + // to prevent creating scope function at each loop + var func = function(methodName) { + var origMethod = that[methodName], ret; - that[methodName] = function() { - args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); - ret = origMethod.apply(that, arguments); + that[methodName] = function() { + args = _simplifyArray(Array.prototype.slice.call(arguments, 0)); + ret = origMethod.apply(that, arguments); - that._trace({ - method: methodName, - args: args - }); + that._trace({ + method: methodName, + args: args + }); - return ret; - }; - }; - // methods - for (n = 0; n < len; n++) { - func(CONTEXT_METHODS[n]); - } + return ret; + }; + }; + // methods + for (n = 0; n < len; n++) { + func(CONTEXT_METHODS[n]); + } - // attrs - that.setAttr = function() { - origSetter.apply(that, arguments); - var prop = arguments[0]; - var val = arguments[1]; - if ((prop === 'shadowOffsetX') || (prop === 'shadowOffsetY') || (prop === 'shadowBlur')) { - val = val / this.canvas.getPixelRatio(); - } - that._trace({ - property: prop, - val: val - }); - }; + // attrs + that.setAttr = function() { + origSetter.apply(that, arguments); + var prop = arguments[0]; + var val = arguments[1]; + if ( + prop === 'shadowOffsetX' || + prop === 'shadowOffsetY' || + prop === 'shadowBlur' + ) { + val = val / this.canvas.getPixelRatio(); } - }; - - CONTEXT_PROPERTIES.forEach(function(prop) { - Object.defineProperty(Konva.Context.prototype, prop, { - get: function () { - return this._context[prop]; - }, - set: function (val) { - this._context[prop] = val; - } + that._trace({ + property: prop, + val: val }); + }; + } + }; + + CONTEXT_PROPERTIES.forEach(function(prop) { + Object.defineProperty(Konva.Context.prototype, prop, { + get: function() { + return this._context[prop]; + }, + set: function(val) { + this._context[prop] = val; + } }); + }); - Konva.SceneContext = function(canvas) { - Konva.Context.call(this, canvas); - }; + Konva.SceneContext = function(canvas) { + Konva.Context.call(this, canvas); + }; - Konva.SceneContext.prototype = { - _fillColor: function(shape) { - var fill = shape.fill(); + Konva.SceneContext.prototype = { + _fillColor: function(shape) { + var fill = shape.fill(); - this.setAttr('fillStyle', fill); - shape._fillFunc(this); - }, - _fillPattern: function(shape) { - var fillPatternX = shape.getFillPatternX(), - fillPatternY = shape.getFillPatternY(), - fillPatternScale = shape.getFillPatternScale(), - fillPatternRotation = Konva.getAngle(shape.getFillPatternRotation()), - fillPatternOffset = shape.getFillPatternOffset(); + this.setAttr('fillStyle', fill); + shape._fillFunc(this); + }, + _fillPattern: function(shape) { + var fillPatternX = shape.getFillPatternX(), + fillPatternY = shape.getFillPatternY(), + fillPatternScale = shape.getFillPatternScale(), + fillPatternRotation = Konva.getAngle(shape.getFillPatternRotation()), + fillPatternOffset = shape.getFillPatternOffset(); - if(fillPatternX || fillPatternY) { - this.translate(fillPatternX || 0, fillPatternY || 0); - } - if(fillPatternRotation) { - this.rotate(fillPatternRotation); - } - if(fillPatternScale) { - this.scale(fillPatternScale.x, fillPatternScale.y); - } - if(fillPatternOffset) { - this.translate(-1 * fillPatternOffset.x, -1 * fillPatternOffset.y); - } + if (fillPatternX || fillPatternY) { + this.translate(fillPatternX || 0, fillPatternY || 0); + } + if (fillPatternRotation) { + this.rotate(fillPatternRotation); + } + if (fillPatternScale) { + this.scale(fillPatternScale.x, fillPatternScale.y); + } + if (fillPatternOffset) { + this.translate(-1 * fillPatternOffset.x, -1 * fillPatternOffset.y); + } - this.setAttr('fillStyle', this.createPattern(shape.getFillPatternImage(), shape.getFillPatternRepeat() || 'repeat')); - this.fill(); - }, - _fillLinearGradient: function(shape) { - var start = shape.getFillLinearGradientStartPoint(), - end = shape.getFillLinearGradientEndPoint(), - colorStops = shape.getFillLinearGradientColorStops(), - grd = this.createLinearGradient(start.x, start.y, end.x, end.y); + this.setAttr( + 'fillStyle', + this.createPattern( + shape.getFillPatternImage(), + shape.getFillPatternRepeat() || 'repeat' + ) + ); + this.fill(); + }, + _fillLinearGradient: function(shape) { + var start = shape.getFillLinearGradientStartPoint(), + end = shape.getFillLinearGradientEndPoint(), + colorStops = shape.getFillLinearGradientColorStops(), + grd = this.createLinearGradient(start.x, start.y, end.x, end.y); - if (colorStops) { - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - this.setAttr('fillStyle', grd); - shape._fillFunc(this); - } - }, - _fillRadialGradient: function(shape) { - var start = shape.getFillRadialGradientStartPoint(), - end = shape.getFillRadialGradientEndPoint(), - startRadius = shape.getFillRadialGradientStartRadius(), - endRadius = shape.getFillRadialGradientEndRadius(), - colorStops = shape.getFillRadialGradientColorStops(), - grd = this.createRadialGradient(start.x, start.y, startRadius, end.x, end.y, endRadius); - - // build color stops - for(var n = 0; n < colorStops.length; n += 2) { - grd.addColorStop(colorStops[n], colorStops[n + 1]); - } - this.setAttr('fillStyle', grd); - this.fill(); - }, - _fill: function(shape) { - var hasColor = shape.fill(), - hasPattern = shape.getFillPatternImage(), - hasLinearGradient = shape.getFillLinearGradientColorStops(), - hasRadialGradient = shape.getFillRadialGradientColorStops(), - fillPriority = shape.getFillPriority(); - - // priority fills - if(hasColor && fillPriority === 'color') { - this._fillColor(shape); - } - else if(hasPattern && fillPriority === 'pattern') { - this._fillPattern(shape); - } - else if(hasLinearGradient && fillPriority === 'linear-gradient') { - this._fillLinearGradient(shape); - } - else if(hasRadialGradient && fillPriority === 'radial-gradient') { - this._fillRadialGradient(shape); - } - // now just try and fill with whatever is available - else if(hasColor) { - this._fillColor(shape); - } - else if(hasPattern) { - this._fillPattern(shape); - } - else if(hasLinearGradient) { - this._fillLinearGradient(shape); - } - else if(hasRadialGradient) { - this._fillRadialGradient(shape); - } - }, - _stroke: function(shape) { - var dash = shape.dash(), - // ignore strokeScaleEnabled for Text - strokeScaleEnabled = (shape.getStrokeScaleEnabled() || (shape instanceof Konva.Text)); - - if(shape.hasStroke()) { - if (!strokeScaleEnabled) { - this.save(); - this.setTransform(1, 0, 0, 1, 0, 0); - } - - this._applyLineCap(shape); - if(dash && shape.dashEnabled()) { - this.setLineDash(dash); - } - - this.setAttr('lineWidth', shape.strokeWidth()); - this.setAttr('strokeStyle', shape.stroke()); - - if (!shape.getShadowForStrokeEnabled()) { - this.setAttr('shadowColor', 'rgba(0,0,0,0)'); - } - shape._strokeFunc(this); - - if (!strokeScaleEnabled) { - this.restore(); - } - } - }, - _applyShadow: function(shape) { - var util = Konva.Util, - color = util.get(shape.getShadowRGBA(), 'black'), - blur = util.get(shape.getShadowBlur(), 5), - offset = util.get(shape.getShadowOffset(), { - x: 0, - y: 0 - }), - // TODO: get this info from transform?? - scale = shape.getAbsoluteScale(), - ratio = this.canvas.getPixelRatio(), - scaleX = scale.x * ratio, - scaleY = scale.y * ratio; - - this.setAttr('shadowColor', color); - this.setAttr('shadowBlur', blur * ratio * Math.min(scaleX, scaleY)); - this.setAttr('shadowOffsetX', offset.x * scaleX); - this.setAttr('shadowOffsetY', offset.y * scaleY); + if (colorStops) { + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); } - }; - Konva.Util.extend(Konva.SceneContext, Konva.Context); + this.setAttr('fillStyle', grd); + shape._fillFunc(this); + } + }, + _fillRadialGradient: function(shape) { + var start = shape.getFillRadialGradientStartPoint(), + end = shape.getFillRadialGradientEndPoint(), + startRadius = shape.getFillRadialGradientStartRadius(), + endRadius = shape.getFillRadialGradientEndRadius(), + colorStops = shape.getFillRadialGradientColorStops(), + grd = this.createRadialGradient( + start.x, + start.y, + startRadius, + end.x, + end.y, + endRadius + ); - Konva.HitContext = function(canvas) { - Konva.Context.call(this, canvas); - }; + // build color stops + for (var n = 0; n < colorStops.length; n += 2) { + grd.addColorStop(colorStops[n], colorStops[n + 1]); + } + this.setAttr('fillStyle', grd); + this.fill(); + }, + _fill: function(shape) { + var hasColor = shape.fill(), + hasPattern = shape.getFillPatternImage(), + hasLinearGradient = shape.getFillLinearGradientColorStops(), + hasRadialGradient = shape.getFillRadialGradientColorStops(), + fillPriority = shape.getFillPriority(); - Konva.HitContext.prototype = { - _fill: function(shape) { - this.save(); - this.setAttr('fillStyle', shape.colorKey); - shape._fillFuncHit(this); - this.restore(); - }, - _stroke: function(shape) { - if(shape.hasStroke() && shape.strokeHitEnabled()) { - // ignore strokeScaleEnabled for Text - var strokeScaleEnabled = (shape.getStrokeScaleEnabled() || (shape instanceof Konva.Text)); - if (!strokeScaleEnabled) { - this.save(); - this.setTransform(1, 0, 0, 1, 0, 0); - } - this._applyLineCap(shape); - this.setAttr('lineWidth', shape.strokeWidth()); - this.setAttr('strokeStyle', shape.colorKey); - shape._strokeFuncHit(this); - if (!strokeScaleEnabled) { - this.restore(); - } - } + // priority fills + if (hasColor && fillPriority === 'color') { + this._fillColor(shape); + } else if (hasPattern && fillPriority === 'pattern') { + this._fillPattern(shape); + } else if (hasLinearGradient && fillPriority === 'linear-gradient') { + this._fillLinearGradient(shape); + } else if (hasRadialGradient && fillPriority === 'radial-gradient') { + this._fillRadialGradient(shape); + } else if (hasColor) { + // now just try and fill with whatever is available + this._fillColor(shape); + } else if (hasPattern) { + this._fillPattern(shape); + } else if (hasLinearGradient) { + this._fillLinearGradient(shape); + } else if (hasRadialGradient) { + this._fillRadialGradient(shape); + } + }, + _stroke: function(shape) { + var dash = shape.dash(), + // ignore strokeScaleEnabled for Text + strokeScaleEnabled = + shape.getStrokeScaleEnabled() || shape instanceof Konva.Text; + + if (shape.hasStroke()) { + if (!strokeScaleEnabled) { + this.save(); + this.setTransform(1, 0, 0, 1, 0, 0); } - }; - Konva.Util.extend(Konva.HitContext, Konva.Context); + + this._applyLineCap(shape); + if (dash && shape.dashEnabled()) { + this.setLineDash(dash); + this.setAttr('lineDashOffset', shape.dashOffset()); + } + + this.setAttr('lineWidth', shape.strokeWidth()); + this.setAttr('strokeStyle', shape.stroke()); + + if (!shape.getShadowForStrokeEnabled()) { + this.setAttr('shadowColor', 'rgba(0,0,0,0)'); + } + shape._strokeFunc(this); + + if (!strokeScaleEnabled) { + this.restore(); + } + } + }, + _applyShadow: function(shape) { + var util = Konva.Util, + color = util.get(shape.getShadowRGBA(), 'black'), + blur = util.get(shape.getShadowBlur(), 5), + offset = util.get(shape.getShadowOffset(), { + x: 0, + y: 0 + }), + // TODO: get this info from transform?? + scale = shape.getAbsoluteScale(), + ratio = this.canvas.getPixelRatio(), + scaleX = scale.x * ratio, + scaleY = scale.y * ratio; + + this.setAttr('shadowColor', color); + this.setAttr( + 'shadowBlur', + blur * ratio * Math.min(Math.abs(scaleX), Math.abs(scaleY)) + ); + this.setAttr('shadowOffsetX', offset.x * scaleX); + this.setAttr('shadowOffsetY', offset.y * scaleY); + }, + _applyGlobalCompositeOperation: function(shape) { + var globalCompositeOperation = shape.getGlobalCompositeOperation(); + if (globalCompositeOperation !== 'source-over') { + this.setAttr('globalCompositeOperation', globalCompositeOperation); + } + } + }; + Konva.Util.extend(Konva.SceneContext, Konva.Context); + + Konva.HitContext = function(canvas) { + Konva.Context.call(this, canvas); + }; + + Konva.HitContext.prototype = { + _fill: function(shape) { + this.save(); + this.setAttr('fillStyle', shape.colorKey); + shape._fillFuncHit(this); + this.restore(); + }, + _stroke: function(shape) { + if (shape.hasStroke() && shape.strokeHitEnabled()) { + // ignore strokeScaleEnabled for Text + var strokeScaleEnabled = + shape.getStrokeScaleEnabled() || shape instanceof Konva.Text; + if (!strokeScaleEnabled) { + this.save(); + this.setTransform(1, 0, 0, 1, 0, 0); + } + this._applyLineCap(shape); + this.setAttr('lineWidth', shape.strokeWidth()); + this.setAttr('strokeStyle', shape.colorKey); + shape._strokeFuncHit(this); + if (!strokeScaleEnabled) { + this.restore(); + } + } + } + }; + Konva.Util.extend(Konva.HitContext, Konva.Context); })(); diff --git a/src/Node.js b/src/Node.js index 8a965582..844153a8 100644 --- a/src/Node.js +++ b/src/Node.js @@ -1,48 +1,43 @@ (function(Konva) { - 'use strict'; - // CONSTANTS - var ABSOLUTE_OPACITY = 'absoluteOpacity', - ABSOLUTE_TRANSFORM = 'absoluteTransform', - ABSOLUTE_SCALE = 'absoluteScale', - CHANGE = 'Change', - CHILDREN = 'children', - DOT = '.', - EMPTY_STRING = '', - GET = 'get', - ID = 'id', - KONVA = 'konva', - LISTENING = 'listening', - MOUSEENTER = 'mouseenter', - MOUSELEAVE = 'mouseleave', - NAME = 'name', - SET = 'set', - SHAPE = 'Shape', - SPACE = ' ', - STAGE = 'stage', - TRANSFORM = 'transform', - UPPER_STAGE = 'Stage', - VISIBLE = 'visible', - CLONE_BLACK_LIST = ['id'], + 'use strict'; + // CONSTANTS + var ABSOLUTE_OPACITY = 'absoluteOpacity', + ABSOLUTE_TRANSFORM = 'absoluteTransform', + ABSOLUTE_SCALE = 'absoluteScale', + CHANGE = 'Change', + CHILDREN = 'children', + DOT = '.', + EMPTY_STRING = '', + GET = 'get', + ID = 'id', + KONVA = 'konva', + LISTENING = 'listening', + MOUSEENTER = 'mouseenter', + MOUSELEAVE = 'mouseleave', + NAME = 'name', + SET = 'set', + SHAPE = 'Shape', + SPACE = ' ', + STAGE = 'stage', + TRANSFORM = 'transform', + UPPER_STAGE = 'Stage', + VISIBLE = 'visible', + CLONE_BLACK_LIST = ['id'], + TRANSFORM_CHANGE_STR = [ + 'xChange.konva', + 'yChange.konva', + 'scaleXChange.konva', + 'scaleYChange.konva', + 'skewXChange.konva', + 'skewYChange.konva', + 'rotationChange.konva', + 'offsetXChange.konva', + 'offsetYChange.konva', + 'transformsEnabledChange.konva' + ].join(SPACE), + SCALE_CHANGE_STR = ['scaleXChange.konva', 'scaleYChange.konva'].join(SPACE); - TRANSFORM_CHANGE_STR = [ - 'xChange.konva', - 'yChange.konva', - 'scaleXChange.konva', - 'scaleYChange.konva', - 'skewXChange.konva', - 'skewYChange.konva', - 'rotationChange.konva', - 'offsetXChange.konva', - 'offsetYChange.konva', - 'transformsEnabledChange.konva' - ].join(SPACE), - - SCALE_CHANGE_STR = [ - 'scaleXChange.konva', - 'scaleYChange.konva' - ].join(SPACE); - - /** + /** * Node constructor. Nodes are entities that can be transformed, layered, * and have bound events. The stage, layers, groups, and shapes all extend Node. * @constructor @@ -51,73 +46,72 @@ * @param {Object} config * @@nodeParams */ - Konva.Node = function(config) { - this._init(config); - }; + Konva.Node = function(config) { + this._init(config); + }; - Konva.Util.addMethods(Konva.Node, { - _init: function(config) { - var that = this; - this._id = Konva.idCounter++; - this.eventListeners = {}; - this.attrs = {}; - this._cache = {}; - this._filterUpToDate = false; - this._isUnderCache = false; - this.setAttrs(config); + Konva.Util.addMethods(Konva.Node, { + _init: function(config) { + var that = this; + this._id = Konva.idCounter++; + this.eventListeners = {}; + this.attrs = {}; + this._cache = {}; + this._filterUpToDate = false; + this._isUnderCache = false; + this.setAttrs(config); - // event bindings for cache handling - this.on(TRANSFORM_CHANGE_STR, function() { - this._clearCache(TRANSFORM); - that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - }); + // event bindings for cache handling + this.on(TRANSFORM_CHANGE_STR, function() { + this._clearCache(TRANSFORM); + that._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }); - this.on(SCALE_CHANGE_STR, function() { - that._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - }); + this.on(SCALE_CHANGE_STR, function() { + that._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + }); - this.on('visibleChange.konva', function() { - that._clearSelfAndDescendantCache(VISIBLE); - }); - this.on('listeningChange.konva', function() { - that._clearSelfAndDescendantCache(LISTENING); - }); - this.on('opacityChange.konva', function() { - that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - }); - }, - _clearCache: function(attr){ - if (attr) { - delete this._cache[attr]; - } - else { - this._cache = {}; - } - }, - _getCache: function(attr, privateGetter){ - var cache = this._cache[attr]; + this.on('visibleChange.konva', function() { + that._clearSelfAndDescendantCache(VISIBLE); + }); + this.on('listeningChange.konva', function() { + that._clearSelfAndDescendantCache(LISTENING); + }); + this.on('opacityChange.konva', function() { + that._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + }); + }, + _clearCache: function(attr) { + if (attr) { + delete this._cache[attr]; + } else { + this._cache = {}; + } + }, + _getCache: function(attr, privateGetter) { + var cache = this._cache[attr]; - // if not cached, we need to set it using the private getter method. - if (cache === undefined) { - this._cache[attr] = privateGetter.call(this); - } + // if not cached, we need to set it using the private getter method. + if (cache === undefined) { + this._cache[attr] = privateGetter.call(this); + } - return this._cache[attr]; - }, - /* + return this._cache[attr]; + }, + /* * when the logic for a cached result depends on ancestor propagation, use this * method to clear self and children cache */ - _clearSelfAndDescendantCache: function(attr) { - this._clearCache(attr); + _clearSelfAndDescendantCache: function(attr) { + this._clearCache(attr); - if (this.children) { - this.getChildren().each(function(node) { - node._clearSelfAndDescendantCache(attr); - }); - } - }, - /** + if (this.children) { + this.getChildren().each(function(node) { + node._clearSelfAndDescendantCache(attr); + }); + } + }, + /** * clear cached canvas * @method * @memberof Konva.Node.prototype @@ -125,12 +119,12 @@ * @example * node.clearCache(); */ - clearCache: function() { - delete this._cache.canvas; - this._filterUpToDate = false; - return this; - }, - /** + clearCache: function() { + delete this._cache.canvas; + this._filterUpToDate = false; + return this; + }, + /** * cache node to improve drawing performance, apply filters, or create more accurate * hit regions. For all basic shapes size of cache canvas will be automatically detected. * If you need to cache your custom `Konva.Shape` instance you have to pass shape's bounding box @@ -145,6 +139,7 @@ * @param {Number} [config.offset] increase canvas size by `offset` pixel in all directions. * @param {Boolean} [config.drawBorder] when set to true, a red border will be drawn around the cached * region for debugging purposes + * @param {Number} [config.pixelRatio] change quality (or pixel ratio) of cached image. pixelRatio = 2 will produce 2x sized cache. * @returns {Konva.Node} * @example * // cache a shape with the x,y position of the bounding box at the center and @@ -171,92 +166,91 @@ * drawBorder: true * }); */ - cache: function(config) { - var conf = config || {}, - rect = this.getClientRect(true), - width = conf.width || rect.width, - height = conf.height || rect.height, - pixelRatio = conf.pixelRatio, - x = conf.x || rect.x, - y = conf.y || rect.y, - offset = conf.offset || 0, - drawBorder = conf.drawBorder || false; + cache: function(config) { + var conf = config || {}, + rect = this.getClientRect(true), + width = conf.width || rect.width, + height = conf.height || rect.height, + pixelRatio = conf.pixelRatio, + x = conf.x || rect.x, + y = conf.y || rect.y, + offset = conf.offset || 0, + drawBorder = conf.drawBorder || false; - if (!width || !height) { - throw new Error('Width or height of caching configuration equals 0.'); - } + if (!width || !height) { + throw new Error('Width or height of caching configuration equals 0.'); + } - width += offset * 2; - height += offset * 2; + width += offset * 2; + height += offset * 2; - x -= offset; - y -= offset; + x -= offset; + y -= offset; + var cachedSceneCanvas = new Konva.SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height + }), + cachedFilterCanvas = new Konva.SceneCanvas({ + pixelRatio: pixelRatio, + width: width, + height: height + }), + cachedHitCanvas = new Konva.HitCanvas({ + pixelRatio: 1, + width: width, + height: height + }), + sceneContext = cachedSceneCanvas.getContext(), + hitContext = cachedHitCanvas.getContext(); - var cachedSceneCanvas = new Konva.SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height - }), - cachedFilterCanvas = new Konva.SceneCanvas({ - pixelRatio: pixelRatio, - width: width, - height: height - }), - cachedHitCanvas = new Konva.HitCanvas({ - pixelRatio: 1, - width: width, - height: height - }), - sceneContext = cachedSceneCanvas.getContext(), - hitContext = cachedHitCanvas.getContext(); + cachedHitCanvas.isCache = true; - cachedHitCanvas.isCache = true; + this.clearCache(); - this.clearCache(); + sceneContext.save(); + hitContext.save(); - sceneContext.save(); - hitContext.save(); + sceneContext.translate(-x, -y); + hitContext.translate(-x, -y); - sceneContext.translate(-x, -y); - hitContext.translate(-x, -y); + // extra flag to skip on getAbsolute opacity calc + this._isUnderCache = true; + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); - // extra flag to skip on getAbsolute opacity calc - this._isUnderCache = true; - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - this._clearSelfAndDescendantCache(ABSOLUTE_SCALE); + this.drawScene(cachedSceneCanvas, this, true); + this.drawHit(cachedHitCanvas, this, true); + this._isUnderCache = false; - this.drawScene(cachedSceneCanvas, this, true); - this.drawHit(cachedHitCanvas, this, true); - this._isUnderCache = false; + sceneContext.restore(); + hitContext.restore(); - sceneContext.restore(); - hitContext.restore(); + // this will draw a red border around the cached box for + // debugging purposes + if (drawBorder) { + sceneContext.save(); + sceneContext.beginPath(); + sceneContext.rect(0, 0, width, height); + sceneContext.closePath(); + sceneContext.setAttr('strokeStyle', 'red'); + sceneContext.setAttr('lineWidth', 5); + sceneContext.stroke(); + sceneContext.restore(); + } - // this will draw a red border around the cached box for - // debugging purposes - if (drawBorder) { - sceneContext.save(); - sceneContext.beginPath(); - sceneContext.rect(0, 0, width, height); - sceneContext.closePath(); - sceneContext.setAttr('strokeStyle', 'red'); - sceneContext.setAttr('lineWidth', 5); - sceneContext.stroke(); - sceneContext.restore(); - } + this._cache.canvas = { + scene: cachedSceneCanvas, + filter: cachedFilterCanvas, + hit: cachedHitCanvas, + x: x, + y: y + }; - this._cache.canvas = { - scene: cachedSceneCanvas, - filter: cachedFilterCanvas, - hit: cachedHitCanvas, - x: x, - y: y - }; - - return this; - }, - /** + return this; + }, + /** * Return client rectangle {x, y, width, height} of node. This rectangle also include all styling (strokes, shadows, etc). * The rectangle position is relative to parent container. * @method @@ -288,109 +282,123 @@ * rect.getClientRect(); * // returns Object {x: -2, y: 46, width: 104, height: 208} */ - getClientRect: function() { - // abstract method - // redefine in Container and Shape - throw new Error('abstract "getClientRect" method call'); - }, - _transformedRect: function(rect) { - var points = [ - {x: rect.x, y: rect.y}, - {x: rect.x + rect.width, y: rect.y}, - {x: rect.x + rect.width, y: rect.y + rect.height}, - {x: rect.x, y: rect.y + rect.height} - ]; - var minX, minY, maxX, maxY; - var trans = this.getTransform(); - points.forEach(function(point) { - var transformed = trans.point(point); - if (minX === undefined) { - minX = maxX = transformed.x; - minY = maxY = transformed.y; - } - minX = Math.min(minX, transformed.x); - minY = Math.min(minY, transformed.y); - maxX = Math.max(maxX, transformed.x); - maxY = Math.max(maxY, transformed.y); - }); - return { - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY - }; - }, - _drawCachedSceneCanvas: function(context) { - context.save(); - context._applyOpacity(this); - context.translate( - this._cache.canvas.x, - this._cache.canvas.y + getClientRect: function() { + // abstract method + // redefine in Container and Shape + throw new Error('abstract "getClientRect" method call'); + }, + _transformedRect: function(rect) { + var points = [ + { x: rect.x, y: rect.y }, + { x: rect.x + rect.width, y: rect.y }, + { x: rect.x + rect.width, y: rect.y + rect.height }, + { x: rect.x, y: rect.y + rect.height } + ]; + var minX, minY, maxX, maxY; + var trans = this.getTransform(); + points.forEach(function(point) { + var transformed = trans.point(point); + if (minX === undefined) { + minX = maxX = transformed.x; + minY = maxY = transformed.y; + } + minX = Math.min(minX, transformed.x); + minY = Math.min(minY, transformed.y); + maxX = Math.max(maxX, transformed.x); + maxY = Math.max(maxY, transformed.y); + }); + return { + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY + }; + }, + _drawCachedSceneCanvas: function(context) { + context.save(); + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.translate(this._cache.canvas.x, this._cache.canvas.y); + + var cacheCanvas = this._getCachedSceneCanvas(); + var ratio = cacheCanvas.pixelRatio; + + context.drawImage( + cacheCanvas._canvas, + 0, + 0, + cacheCanvas.width / ratio, + cacheCanvas.height / ratio + ); + context.restore(); + }, + _drawCachedHitCanvas: function(context) { + var cachedCanvas = this._cache.canvas, hitCanvas = cachedCanvas.hit; + context.save(); + context.translate(this._cache.canvas.x, this._cache.canvas.y); + context.drawImage(hitCanvas._canvas, 0, 0); + context.restore(); + }, + _getCachedSceneCanvas: function() { + var filters = this.filters(), + cachedCanvas = this._cache.canvas, + sceneCanvas = cachedCanvas.scene, + filterCanvas = cachedCanvas.filter, + filterContext = filterCanvas.getContext(), + len, + imageData, + n, + filter; + + if (filters) { + if (!this._filterUpToDate) { + var ratio = sceneCanvas.pixelRatio; + + try { + len = filters.length; + filterContext.clear(); + + // copy cached canvas onto filter context + filterContext.drawImage( + sceneCanvas._canvas, + 0, + 0, + sceneCanvas.getWidth() / ratio, + sceneCanvas.getHeight() / ratio + ); + imageData = filterContext.getImageData( + 0, + 0, + filterCanvas.getWidth(), + filterCanvas.getHeight() ); - var cacheCanvas = this._getCachedSceneCanvas(); - var ratio = cacheCanvas.pixelRatio; - - context.drawImage(cacheCanvas._canvas, 0, 0, cacheCanvas.width / ratio, cacheCanvas.height / ratio); - context.restore(); - }, - _drawCachedHitCanvas: function(context) { - var cachedCanvas = this._cache.canvas, - hitCanvas = cachedCanvas.hit; - context.save(); - context.translate( - this._cache.canvas.x, - this._cache.canvas.y - ); - context.drawImage(hitCanvas._canvas, 0, 0); - context.restore(); - }, - _getCachedSceneCanvas: function() { - var filters = this.filters(), - cachedCanvas = this._cache.canvas, - sceneCanvas = cachedCanvas.scene, - filterCanvas = cachedCanvas.filter, - filterContext = filterCanvas.getContext(), - len, imageData, n, filter; - - if (filters) { - if (!this._filterUpToDate) { - var ratio = sceneCanvas.pixelRatio; - - try { - len = filters.length; - filterContext.clear(); - - // copy cached canvas onto filter context - filterContext.drawImage(sceneCanvas._canvas, 0, 0, sceneCanvas.getWidth() / ratio, sceneCanvas.getHeight() / ratio); - imageData = filterContext.getImageData(0, 0, filterCanvas.getWidth(), filterCanvas.getHeight()); - - // apply filters to filter context - for (n = 0; n < len; n++) { - filter = filters[n]; - if (typeof filter !== 'function') { - Konva.Util.error( - 'Filter should be type of function, but got ' + - (typeof filter) + ' insted. Please check correct filters' - ); - continue; - } - filter.call(this, imageData); - filterContext.putImageData(imageData, 0, 0); - } - } - catch(e) { - Konva.Util.error('Unable to apply filter. ' + e.message); - } - - this._filterUpToDate = true; - } - - return filterCanvas; + // apply filters to filter context + for (n = 0; n < len; n++) { + filter = filters[n]; + if (typeof filter !== 'function') { + Konva.Util.error( + 'Filter should be type of function, but got ' + + typeof filter + + ' insted. Please check correct filters' + ); + continue; + } + filter.call(this, imageData); + filterContext.putImageData(imageData, 0, 0); } - return sceneCanvas; - }, - /** + } catch (e) { + Konva.Util.error('Unable to apply filter. ' + e.message); + } + + this._filterUpToDate = true; + } + + return filterCanvas; + } + return sceneCanvas; + }, + /** * bind events to the node. KonvaJS supports mouseover, mousemove, * mouseout, mouseenter, mouseleave, mousedown, mouseup, wheel, click, dblclick, touchstart, touchmove, * touchend, tap, dbltap, dragstart, dragmove, and dragend events. The Konva Stage supports @@ -453,39 +461,43 @@ * var group = evtn.currentTarger; * }); */ - on: function(evtStr, handler) { - if (arguments.length === 3) { - return this._delegate.apply(this, arguments); - } - var events = evtStr.split(SPACE), - len = events.length, - n, event, parts, baseEvent, name; + on: function(evtStr, handler) { + if (arguments.length === 3) { + return this._delegate.apply(this, arguments); + } + var events = evtStr.split(SPACE), + len = events.length, + n, + event, + parts, + baseEvent, + name; - /* + /* * loop through types and attach event listeners to * each one. eg. 'click mouseover.namespace mouseout' * will create three event bindings */ - for(n = 0; n < len; n++) { - event = events[n]; - parts = event.split(DOT); - baseEvent = parts[0]; - name = parts[1] || EMPTY_STRING; + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1] || EMPTY_STRING; - // create events array if it doesn't exist - if(!this.eventListeners[baseEvent]) { - this.eventListeners[baseEvent] = []; - } + // create events array if it doesn't exist + if (!this.eventListeners[baseEvent]) { + this.eventListeners[baseEvent] = []; + } - this.eventListeners[baseEvent].push({ - name: name, - handler: handler - }); - } + this.eventListeners[baseEvent].push({ + name: name, + handler: handler + }); + } - return this; - }, - /** + return this; + }, + /** * remove event bindings from the node. Pass in a string of * event types delimmited by a space to remove multiple event * bindings at once such as 'mousedown mouseup mousemove'. @@ -506,70 +518,74 @@ * // remove listener by name * node.off('click.foo'); */ - off: function(evtStr) { - var events = (evtStr || '').split(SPACE), - len = events.length, - n, t, event, parts, baseEvent, name; + off: function(evtStr) { + var events = (evtStr || '').split(SPACE), + len = events.length, + n, + t, + event, + parts, + baseEvent, + name; - if (!evtStr) { - // remove all events - for(t in this.eventListeners) { - this._off(t); - } - } - for(n = 0; n < len; n++) { - event = events[n]; - parts = event.split(DOT); - baseEvent = parts[0]; - name = parts[1]; + if (!evtStr) { + // remove all events + for (t in this.eventListeners) { + this._off(t); + } + } + for (n = 0; n < len; n++) { + event = events[n]; + parts = event.split(DOT); + baseEvent = parts[0]; + name = parts[1]; - if(baseEvent) { - if(this.eventListeners[baseEvent]) { - this._off(baseEvent, name); - } - } - else { - for(t in this.eventListeners) { - this._off(t, name); - } - } - } - return this; - }, - // some event aliases for third party integration like HammerJS - dispatchEvent: function(evt) { - var e = { - target: this, - type: evt.type, - evt: evt - }; - this.fire(evt.type, e); - return this; - }, - addEventListener: function(type, handler) { - // we have to pass native event to handler - this.on(type, function(evt){ - handler.call(this, evt.evt); - }); - return this; - }, - removeEventListener: function(type) { - this.off(type); - return this; - }, - // like node.on - _delegate: function(event, selector, handler) { - var stopNode = this; - this.on(event, function(evt) { - var targets = evt.target.findAncestors(selector, true, stopNode); - for(var i = 0; i < targets.length; i++) { - evt = Konva.Util.cloneObject(evt); - evt.currentTarget = targets[i]; - handler.call(targets[i], evt); - } - }); - }, - /** + if (baseEvent) { + if (this.eventListeners[baseEvent]) { + this._off(baseEvent, name); + } + } else { + for (t in this.eventListeners) { + this._off(t, name); + } + } + } + return this; + }, + // some event aliases for third party integration like HammerJS + dispatchEvent: function(evt) { + var e = { + target: this, + type: evt.type, + evt: evt + }; + this.fire(evt.type, e); + return this; + }, + addEventListener: function(type, handler) { + // we have to pass native event to handler + this.on(type, function(evt) { + handler.call(this, evt.evt); + }); + return this; + }, + removeEventListener: function(type) { + this.off(type); + return this; + }, + // like node.on + _delegate: function(event, selector, handler) { + var stopNode = this; + this.on(event, function(evt) { + var targets = evt.target.findAncestors(selector, true, stopNode); + for (var i = 0; i < targets.length; i++) { + evt = Konva.Util.cloneObject(evt); + evt.currentTarget = targets[i]; + handler.call(targets[i], evt); + } + }); + }, + /** * remove self from parent, but don't destroy * @method * @memberof Konva.Node.prototype @@ -577,47 +593,47 @@ * @example * node.remove(); */ - remove: function() { - var parent = this.getParent(); + remove: function() { + var parent = this.getParent(); - if(parent && parent.children) { - parent.children.splice(this.index, 1); - parent._setChildrenIndices(); - delete this.parent; - } + if (parent && parent.children) { + parent.children.splice(this.index, 1); + parent._setChildrenIndices(); + delete this.parent; + } - // every cached attr that is calculated via node tree - // traversal must be cleared when removing a node - this._clearSelfAndDescendantCache(STAGE); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - this._clearSelfAndDescendantCache(VISIBLE); - this._clearSelfAndDescendantCache(LISTENING); - this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); + // every cached attr that is calculated via node tree + // traversal must be cleared when removing a node + this._clearSelfAndDescendantCache(STAGE); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearSelfAndDescendantCache(VISIBLE); + this._clearSelfAndDescendantCache(LISTENING); + this._clearSelfAndDescendantCache(ABSOLUTE_OPACITY); - return this; - }, - /** + return this; + }, + /** * remove and destroy self * @method * @memberof Konva.Node.prototype * @example * node.destroy(); */ - destroy: function() { - // remove from ids and names hashes - Konva._removeId(this.getId()); + destroy: function() { + // remove from ids and names hashes + Konva._removeId(this.getId()); - // remove all names - var names = (this.getName() || '').split(/\s/g); - for(var i = 0; i < names.length; i++) { - var subname = names[i]; - Konva._removeName(subname, this._id); - } + // remove all names + var names = (this.getName() || '').split(/\s/g); + for (var i = 0; i < names.length; i++) { + var subname = names[i]; + Konva._removeName(subname, this._id); + } - this.remove(); - return this; - }, - /** + this.remove(); + return this; + }, + /** * get attr * @method * @memberof Konva.Node.prototype @@ -626,15 +642,15 @@ * @example * var x = node.getAttr('x'); */ - getAttr: function(attr) { - var method = GET + Konva.Util._capitalize(attr); - if(Konva.Util._isFunction(this[method])) { - return this[method](); - } - // otherwise get directly - return this.attrs[attr]; - }, - /** + getAttr: function(attr) { + var method = GET + Konva.Util._capitalize(attr); + if (Konva.Util._isFunction(this[method])) { + return this[method](); + } + // otherwise get directly + return this.attrs[attr]; + }, + /** * get ancestors * @method * @memberof Konva.Node.prototype @@ -644,27 +660,26 @@ * console.log(node.getId()); * }) */ - getAncestors: function() { - var parent = this.getParent(), - ancestors = new Konva.Collection(); + getAncestors: function() { + var parent = this.getParent(), ancestors = new Konva.Collection(); - while (parent) { - ancestors.push(parent); - parent = parent.getParent(); - } + while (parent) { + ancestors.push(parent); + parent = parent.getParent(); + } - return ancestors; - }, - /** + return ancestors; + }, + /** * get attrs object literal * @method * @memberof Konva.Node.prototype * @returns {Object} */ - getAttrs: function() { - return this.attrs || {}; - }, - /** + getAttrs: function() { + return this.attrs || {}; + }, + /** * set multiple attrs at once using an object literal * @method * @memberof Konva.Node.prototype @@ -676,29 +691,28 @@ * fill: 'red' * }); */ - setAttrs: function(config) { - var key, method; + setAttrs: function(config) { + var key, method; - if(!config) { - return this; - } - for(key in config) { - if (key === CHILDREN) { - continue; - } - method = SET + Konva.Util._capitalize(key); - // use setter if available - if(Konva.Util._isFunction(this[method])) { - this[method](config[key]); - } - // otherwise set directly - else { - this._setAttr(key, config[key]); - } - } - return this; - }, - /** + if (!config) { + return this; + } + for (key in config) { + if (key === CHILDREN) { + continue; + } + method = SET + Konva.Util._capitalize(key); + // use setter if available + if (Konva.Util._isFunction(this[method])) { + this[method](config[key]); + } else { + // otherwise set directly + this._setAttr(key, config[key]); + } + } + return this; + }, + /** * determine if node is listening for events by taking into account ancestors. * * Parent | Self | isListening @@ -717,28 +731,25 @@ * @memberof Konva.Node.prototype * @returns {Boolean} */ - isListening: function() { - return this._getCache(LISTENING, this._isListening); - }, - _isListening: function() { - var listening = this.getListening(), - parent = this.getParent(); + isListening: function() { + return this._getCache(LISTENING, this._isListening); + }, + _isListening: function() { + var listening = this.getListening(), parent = this.getParent(); - // the following conditions are a simplification of the truth table above. - // please modify carefully - if (listening === 'inherit') { - if (parent) { - return parent.isListening(); - } - else { - return true; - } - } - else { - return listening; - } - }, - /** + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (listening === 'inherit') { + if (parent) { + return parent.isListening(); + } else { + return true; + } + } else { + return listening; + } + }, + /** * determine if node is visible by taking into account ancestors. * * Parent | Self | isVisible @@ -757,108 +768,107 @@ * @memberof Konva.Node.prototype * @returns {Boolean} */ - isVisible: function() { - return this._getCache(VISIBLE, this._isVisible); - }, - _isVisible: function() { - var visible = this.getVisible(), - parent = this.getParent(); + isVisible: function() { + return this._getCache(VISIBLE, this._isVisible); + }, + _isVisible: function() { + var visible = this.getVisible(), parent = this.getParent(); - // the following conditions are a simplification of the truth table above. - // please modify carefully - if (visible === 'inherit') { - if (parent) { - return parent.isVisible(); - } - else { - return true; - } - } - else { - return visible; - } - }, - /** + // the following conditions are a simplification of the truth table above. + // please modify carefully + if (visible === 'inherit') { + if (parent) { + return parent.isVisible(); + } else { + return true; + } + } else { + return visible; + } + }, + /** * determine if listening is enabled by taking into account descendants. If self or any children * have _isListeningEnabled set to true, then self also has listening enabled. * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - shouldDrawHit: function(canvas) { - var layer = this.getLayer(); - return (canvas && canvas.isCache) || (layer && layer.hitGraphEnabled()) - && this.isListening() && this.isVisible(); - }, - /** + shouldDrawHit: function(canvas) { + var layer = this.getLayer(); + return ( + (canvas && canvas.isCache) || + (layer && + layer.hitGraphEnabled() && + this.isListening() && + this.isVisible()) + ); + }, + /** * show node * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - show: function() { - this.setVisible(true); - return this; - }, - /** + show: function() { + this.setVisible(true); + return this; + }, + /** * hide node. Hidden nodes are no longer detectable * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - hide: function() { - this.setVisible(false); - return this; - }, - /** + hide: function() { + this.setVisible(false); + return this; + }, + /** * get zIndex relative to the node's siblings who share the same parent * @method * @memberof Konva.Node.prototype * @returns {Integer} */ - getZIndex: function() { - return this.index || 0; - }, - /** + getZIndex: function() { + return this.index || 0; + }, + /** * get absolute z-index which takes into account sibling * and ancestor indices * @method * @memberof Konva.Node.prototype * @returns {Integer} */ - getAbsoluteZIndex: function() { - var depth = this.getDepth(), - that = this, - index = 0, - nodes, len, n, child; + getAbsoluteZIndex: function() { + var depth = this.getDepth(), that = this, index = 0, nodes, len, n, child; - function addChildren(children) { - nodes = []; - len = children.length; - for(n = 0; n < len; n++) { - child = children[n]; - index++; + function addChildren(children) { + nodes = []; + len = children.length; + for (n = 0; n < len; n++) { + child = children[n]; + index++; - if(child.nodeType !== SHAPE) { - nodes = nodes.concat(child.getChildren().toArray()); - } + if (child.nodeType !== SHAPE) { + nodes = nodes.concat(child.getChildren().toArray()); + } - if(child._id === that._id) { - n = len; - } - } + if (child._id === that._id) { + n = len; + } + } - if(nodes.length > 0 && nodes[0].getDepth() <= depth) { - addChildren(nodes); - } - } - if(that.nodeType !== UPPER_STAGE) { - addChildren(that.getStage().getChildren()); - } + if (nodes.length > 0 && nodes[0].getDepth() <= depth) { + addChildren(nodes); + } + } + if (that.nodeType !== UPPER_STAGE) { + addChildren(that.getStage().getChildren()); + } - return index; - }, - /** + return index; + }, + /** * get node depth in node tree. Returns an integer. * e.g. Stage depth will always be 0. Layers will always be 1. Groups and Shapes will always * be >= 2 @@ -866,28 +876,27 @@ * @memberof Konva.Node.prototype * @returns {Integer} */ - getDepth: function() { - var depth = 0, - parent = this.parent; + getDepth: function() { + var depth = 0, parent = this.parent; - while(parent) { - depth++; - parent = parent.parent; - } - return depth; - }, - setPosition: function(pos) { - this.setX(pos.x); - this.setY(pos.y); - return this; - }, - getPosition: function() { - return { - x: this.getX(), - y: this.getY() - }; - }, - /** + while (parent) { + depth++; + parent = parent.parent; + } + return depth; + }, + setPosition: function(pos) { + this.setX(pos.x); + this.setY(pos.y); + return this; + }, + getPosition: function() { + return { + x: this.getX(), + y: this.getY() + }; + }, + /** * get absolute position relative to the top left corner of the stage container div * or relative to passed node * @method @@ -895,18 +904,18 @@ * @memberof Konva.Node.prototype * @returns {Object} */ - getAbsolutePosition: function(top) { - var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), - absoluteTransform = new Konva.Transform(), - offset = this.offset(); + getAbsolutePosition: function(top) { + var absoluteMatrix = this.getAbsoluteTransform(top).getMatrix(), + absoluteTransform = new Konva.Transform(), + offset = this.offset(); - // clone the matrix array - absoluteTransform.m = absoluteMatrix.slice(); - absoluteTransform.translate(offset.x, offset.y); + // clone the matrix array + absoluteTransform.m = absoluteMatrix.slice(); + absoluteTransform.translate(offset.x, offset.y); - return absoluteTransform.getTranslation(); - }, - /** + return absoluteTransform.getTranslation(); + }, + /** * set absolute position * @method * @memberof Konva.Node.prototype @@ -915,71 +924,70 @@ * @param {Number} pos.y * @returns {Konva.Node} */ - setAbsolutePosition: function(pos) { - var origTrans = this._clearTransform(), - it; + setAbsolutePosition: function(pos) { + var origTrans = this._clearTransform(), it; - // don't clear translation - this.attrs.x = origTrans.x; - this.attrs.y = origTrans.y; - delete origTrans.x; - delete origTrans.y; + // don't clear translation + this.attrs.x = origTrans.x; + this.attrs.y = origTrans.y; + delete origTrans.x; + delete origTrans.y; - // unravel transform - it = this.getAbsoluteTransform(); + // unravel transform + it = this.getAbsoluteTransform(); - it.invert(); - it.translate(pos.x, pos.y); - pos = { - x: this.attrs.x + it.getTranslation().x, - y: this.attrs.y + it.getTranslation().y - }; + it.invert(); + it.translate(pos.x, pos.y); + pos = { + x: this.attrs.x + it.getTranslation().x, + y: this.attrs.y + it.getTranslation().y + }; - this.setPosition({x: pos.x, y: pos.y}); - this._setTransform(origTrans); + this.setPosition({ x: pos.x, y: pos.y }); + this._setTransform(origTrans); - return this; - }, - _setTransform: function(trans) { - var key; + return this; + }, + _setTransform: function(trans) { + var key; - for(key in trans) { - this.attrs[key] = trans[key]; - } + for (key in trans) { + this.attrs[key] = trans[key]; + } - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - }, - _clearTransform: function() { - var trans = { - x: this.getX(), - y: this.getY(), - rotation: this.getRotation(), - scaleX: this.getScaleX(), - scaleY: this.getScaleY(), - offsetX: this.getOffsetX(), - offsetY: this.getOffsetY(), - skewX: this.getSkewX(), - skewY: this.getSkewY() - }; + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + }, + _clearTransform: function() { + var trans = { + x: this.getX(), + y: this.getY(), + rotation: this.getRotation(), + scaleX: this.getScaleX(), + scaleY: this.getScaleY(), + offsetX: this.getOffsetX(), + offsetY: this.getOffsetY(), + skewX: this.getSkewX(), + skewY: this.getSkewY() + }; - this.attrs.x = 0; - this.attrs.y = 0; - this.attrs.rotation = 0; - this.attrs.scaleX = 1; - this.attrs.scaleY = 1; - this.attrs.offsetX = 0; - this.attrs.offsetY = 0; - this.attrs.skewX = 0; - this.attrs.skewY = 0; + this.attrs.x = 0; + this.attrs.y = 0; + this.attrs.rotation = 0; + this.attrs.scaleX = 1; + this.attrs.scaleY = 1; + this.attrs.offsetX = 0; + this.attrs.offsetY = 0; + this.attrs.skewX = 0; + this.attrs.skewY = 0; - this._clearCache(TRANSFORM); - this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); + this._clearCache(TRANSFORM); + this._clearSelfAndDescendantCache(ABSOLUTE_TRANSFORM); - // return original transform - return trans; - }, - /** + // return original transform + return trans; + }, + /** * move node by an amount relative to its current position * @method * @memberof Konva.Node.prototype @@ -994,173 +1002,172 @@ * y: 2) * }); */ - move: function(change) { - var changeX = change.x, - changeY = change.y, - x = this.getX(), - y = this.getY(); + move: function(change) { + var changeX = change.x, + changeY = change.y, + x = this.getX(), + y = this.getY(); - if(changeX !== undefined) { - x += changeX; - } + if (changeX !== undefined) { + x += changeX; + } - if(changeY !== undefined) { - y += changeY; - } + if (changeY !== undefined) { + y += changeY; + } - this.setPosition({x: x, y: y}); - return this; - }, - _eachAncestorReverse: function(func, top) { - var family = [], - parent = this.getParent(), - len, n; + this.setPosition({ x: x, y: y }); + return this; + }, + _eachAncestorReverse: function(func, top) { + var family = [], parent = this.getParent(), len, n; - // if top node is defined, and this node is top node, - // there's no need to build a family tree. just execute - // func with this because it will be the only node - if (top && top._id === this._id) { - func(this); - return true; - } + // if top node is defined, and this node is top node, + // there's no need to build a family tree. just execute + // func with this because it will be the only node + if (top && top._id === this._id) { + func(this); + return true; + } - family.unshift(this); + family.unshift(this); - while(parent && (!top || parent._id !== top._id)) { - family.unshift(parent); - parent = parent.parent; - } + while (parent && (!top || parent._id !== top._id)) { + family.unshift(parent); + parent = parent.parent; + } - len = family.length; - for(n = 0; n < len; n++) { - func(family[n]); - } - }, - /** + len = family.length; + for (n = 0; n < len; n++) { + func(family[n]); + } + }, + /** * rotate node by an amount in degrees relative to its current rotation * @method * @memberof Konva.Node.prototype * @param {Number} theta * @returns {Konva.Node} */ - rotate: function(theta) { - this.setRotation(this.getRotation() + theta); - return this; - }, - /** + rotate: function(theta) { + this.setRotation(this.getRotation() + theta); + return this; + }, + /** * move node to the top of its siblings * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - moveToTop: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); - return false; - } - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.push(this); - this.parent._setChildrenIndices(); - return true; - }, - /** + moveToTop: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveToTop function is ignored.'); + return false; + } + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.push(this); + this.parent._setChildrenIndices(); + return true; + }, + /** * move node up * @method * @memberof Konva.Node.prototype * @returns {Boolean} flag is moved or not */ - moveUp: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveUp function is ignored.'); - return false; - } - var index = this.index, - len = this.parent.getChildren().length; - if(index < len - 1) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index + 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** + moveUp: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveUp function is ignored.'); + return false; + } + var index = this.index, len = this.parent.getChildren().length; + if (index < len - 1) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index + 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** * move node down * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - moveDown: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveDown function is ignored.'); - return false; - } - var index = this.index; - if(index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.splice(index - 1, 0, this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** + moveDown: function() { + if (!this.parent) { + Konva.Util.warn('Node has no parent. moveDown function is ignored.'); + return false; + } + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.splice(index - 1, 0, this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** * move node to the bottom of its siblings * @method * @memberof Konva.Node.prototype * @returns {Boolean} */ - moveToBottom: function() { - if (!this.parent) { - Konva.Util.warn('Node has no parent. moveToBottom function is ignored.'); - return false; - } - var index = this.index; - if(index > 0) { - this.parent.children.splice(index, 1); - this.parent.children.unshift(this); - this.parent._setChildrenIndices(); - return true; - } - return false; - }, - /** + moveToBottom: function() { + if (!this.parent) { + Konva.Util.warn( + 'Node has no parent. moveToBottom function is ignored.' + ); + return false; + } + var index = this.index; + if (index > 0) { + this.parent.children.splice(index, 1); + this.parent.children.unshift(this); + this.parent._setChildrenIndices(); + return true; + } + return false; + }, + /** * set zIndex relative to siblings * @method * @memberof Konva.Node.prototype * @param {Integer} zIndex * @returns {Konva.Node} */ - setZIndex: function(zIndex) { - if (!this.parent) { - Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); - return false; - } - var index = this.index; - this.parent.children.splice(index, 1); - this.parent.children.splice(zIndex, 0, this); - this.parent._setChildrenIndices(); - return this; - }, - /** + setZIndex: function(zIndex) { + if (!this.parent) { + Konva.Util.warn('Node has no parent. zIndex parameter is ignored.'); + return false; + } + var index = this.index; + this.parent.children.splice(index, 1); + this.parent.children.splice(zIndex, 0, this); + this.parent._setChildrenIndices(); + return this; + }, + /** * get absolute opacity * @method * @memberof Konva.Node.prototype * @returns {Number} */ - getAbsoluteOpacity: function() { - return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); - }, - _getAbsoluteOpacity: function() { - var absOpacity = this.getOpacity(); - var parent = this.getParent(); - if(parent && !parent._isUnderCache) { - absOpacity *= this.getParent().getAbsoluteOpacity(); - } - return absOpacity; - }, - /** + getAbsoluteOpacity: function() { + return this._getCache(ABSOLUTE_OPACITY, this._getAbsoluteOpacity); + }, + _getAbsoluteOpacity: function() { + var absOpacity = this.getOpacity(); + var parent = this.getParent(); + if (parent && !parent._isUnderCache) { + absOpacity *= this.getParent().getAbsoluteOpacity(); + } + return absOpacity; + }, + /** * move node to another container * @method * @memberof Konva.Node.prototype @@ -1170,64 +1177,62 @@ * // move node from current layer into layer2 * node.moveTo(layer2); */ - moveTo: function(newContainer) { - // do nothing if new container is already parent - if (this.getParent() !== newContainer) { - // this.remove my be overrided by drag and drop - // buy we need original - (this.__originalRemove || this.remove).call(this); - newContainer.add(this); - } - return this; - }, - /** + moveTo: function(newContainer) { + // do nothing if new container is already parent + if (this.getParent() !== newContainer) { + // this.remove my be overrided by drag and drop + // buy we need original + (this.__originalRemove || this.remove).call(this); + newContainer.add(this); + } + return this; + }, + /** * convert Node into an object for serialization. Returns an object. * @method * @memberof Konva.Node.prototype * @returns {Object} */ - toObject: function() { - var obj = {}, - attrs = this.getAttrs(), - key, val, getter, defaultValue; + toObject: function() { + var obj = {}, attrs = this.getAttrs(), key, val, getter, defaultValue; - obj.attrs = {}; + obj.attrs = {}; - for(key in attrs) { - val = attrs[key]; - getter = this[key]; - // remove attr value so that we can extract the default value from the getter - delete attrs[key]; - defaultValue = getter ? getter.call(this) : null; - // restore attr value - attrs[key] = val; - if (defaultValue !== val) { - obj.attrs[key] = val; - } - } + for (key in attrs) { + val = attrs[key]; + getter = this[key]; + // remove attr value so that we can extract the default value from the getter + delete attrs[key]; + defaultValue = getter ? getter.call(this) : null; + // restore attr value + attrs[key] = val; + if (defaultValue !== val) { + obj.attrs[key] = val; + } + } - obj.className = this.getClassName(); - return Konva.Util._prepareToStringify(obj); - }, - /** + obj.className = this.getClassName(); + return Konva.Util._prepareToStringify(obj); + }, + /** * convert Node into a JSON string. Returns a JSON string. * @method * @memberof Konva.Node.prototype * @returns {String}} */ - toJSON: function() { - return JSON.stringify(this.toObject()); - }, - /** + toJSON: function() { + return JSON.stringify(this.toObject()); + }, + /** * get parent container * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - getParent: function() { - return this.parent; - }, - /** + getParent: function() { + return this.parent; + }, + /** * get all ancestros (parent then parent of the parent, etc) of the node * @method * @memberof Konva.Node.prototype @@ -1239,25 +1244,25 @@ * // get one of the parent group * var parentGroups = node.findAncestors('Group'); */ - findAncestors: function(selector, includeSelf, stopNode) { - var res = []; + findAncestors: function(selector, includeSelf, stopNode) { + var res = []; - if (includeSelf && this._isMatch(selector)) { - res.push(this); - } - var ancestor = this.parent; - while(ancestor) { - if (ancestor === stopNode) { - return res; - } - if (ancestor._isMatch(selector)) { - res.push(ancestor); - } - ancestor = ancestor.parent; - } - return res; - }, - /** + if (includeSelf && this._isMatch(selector)) { + res.push(this); + } + var ancestor = this.parent; + while (ancestor) { + if (ancestor === stopNode) { + return res; + } + if (ancestor._isMatch(selector)) { + res.push(ancestor); + } + ancestor = ancestor.parent; + } + return res; + }, + /** * get ancestor (parent or parent of the parent, etc) of the node that match passed selector * @method * @memberof Konva.Node.prototype @@ -1269,71 +1274,76 @@ * // get one of the parent group * var group = node.findAncestors('.mygroup'); */ - findAncestor: function(selector, includeSelf, stopNode) { - return this.findAncestors(selector, includeSelf, stopNode)[0]; - }, - // is current node match passed selector? - _isMatch: function(selector) { - if (!selector) { - return false; - } - var selectorArr = selector.replace(/ /g, '').split(','), - len = selectorArr.length, - n, sel; + findAncestor: function(selector, includeSelf, stopNode) { + return this.findAncestors(selector, includeSelf, stopNode)[0]; + }, + // is current node match passed selector? + _isMatch: function(selector) { + if (!selector) { + return false; + } + var selectorArr = selector.replace(/ /g, '').split(','), + len = selectorArr.length, + n, + sel; - for (n = 0; n < len; n++) { - sel = selectorArr[n]; - if (!Konva.Util.isValidSelector(sel)) { - Konva.Util.warn('Selector "' + sel + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".'); - Konva.Util.warn('If you have a custom shape with such className, please change it to start with upper letter like "Triangle".'); - Konva.Util.warn('Konva is awesome, right?'); - } - // id selector - if(sel.charAt(0) === '#') { - if (this.id() === sel.slice(1)) { - return true; - } - } - // name selector - else if(sel.charAt(0) === '.') { - if (this.hasName(sel.slice(1))) { - return true; - } - } else if (this._get(sel).length !== 0) { - return true; - } - } - return false; - }, - /** + for (n = 0; n < len; n++) { + sel = selectorArr[n]; + if (!Konva.Util.isValidSelector(sel)) { + Konva.Util.warn( + 'Selector "' + + sel + + '" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".' + ); + Konva.Util.warn( + 'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".' + ); + Konva.Util.warn('Konva is awesome, right?'); + } + // id selector + if (sel.charAt(0) === '#') { + if (this.id() === sel.slice(1)) { + return true; + } + } else if (sel.charAt(0) === '.') { + // name selector + if (this.hasName(sel.slice(1))) { + return true; + } + } else if (this._get(sel).length !== 0) { + return true; + } + } + return false; + }, + /** * get layer ancestor * @method * @memberof Konva.Node.prototype * @returns {Konva.Layer} */ - getLayer: function() { - var parent = this.getParent(); - return parent ? parent.getLayer() : null; - }, - /** + getLayer: function() { + var parent = this.getParent(); + return parent ? parent.getLayer() : null; + }, + /** * get stage ancestor * @method * @memberof Konva.Node.prototype * @returns {Konva.Stage} */ - getStage: function() { - return this._getCache(STAGE, this._getStage); - }, - _getStage: function() { - var parent = this.getParent(); - if(parent) { - return parent.getStage(); - } - else { - return undefined; - } - }, - /** + getStage: function() { + return this._getCache(STAGE, this._getStage); + }, + _getStage: function() { + var parent = this.getParent(); + if (parent) { + return parent.getStage(); + } else { + return undefined; + } + }, + /** * fire event * @method * @memberof Konva.Node.prototype @@ -1357,134 +1367,128 @@ * // fire click event that bubbles * node.fire('click', null, true); */ - fire: function(eventType, evt, bubble) { - evt = evt || {}; - evt.target = evt.target || this; - // bubble - if (bubble) { - this._fireAndBubble(eventType, evt); - } - // no bubble - else { - this._fire(eventType, evt); - } - return this; - }, - /** + fire: function(eventType, evt, bubble) { + evt = evt || {}; + evt.target = evt.target || this; + // bubble + if (bubble) { + this._fireAndBubble(eventType, evt); + } else { + // no bubble + this._fire(eventType, evt); + } + return this; + }, + /** * get absolute transform of the node which takes into * account its ancestor transforms * @method * @memberof Konva.Node.prototype * @returns {Konva.Transform} */ - getAbsoluteTransform: function(top) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } - // if no argument, we can cache the result - else { - return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); - } - }, - _getAbsoluteTransform: function(top) { - var at = new Konva.Transform(), - transformsEnabled, trans; + getAbsoluteTransform: function(top) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteTransform(top); + } else { + // if no argument, we can cache the result + return this._getCache(ABSOLUTE_TRANSFORM, this._getAbsoluteTransform); + } + }, + _getAbsoluteTransform: function(top) { + var at = new Konva.Transform(), transformsEnabled, trans; - // start with stage and traverse downwards to self - this._eachAncestorReverse(function(node) { - transformsEnabled = node.transformsEnabled(); - trans = node.getTransform(); + // start with stage and traverse downwards to self + this._eachAncestorReverse(function(node) { + transformsEnabled = node.transformsEnabled(); + trans = node.getTransform(); - if (transformsEnabled === 'all') { - at.multiply(trans); - } - else if (transformsEnabled === 'position') { - at.translate(node.x(), node.y()); - } - }, top); - return at; - }, - /** + if (transformsEnabled === 'all') { + at.multiply(trans); + } else if (transformsEnabled === 'position') { + at.translate(node.x(), node.y()); + } + }, top); + return at; + }, + /** * get absolute scale of the node which takes into * account its ancestor scales * @method * @memberof Konva.Node.prototype * @returns {Konva.Transform} */ - getAbsoluteScale: function(top) { - // if using an argument, we can't cache the result. - if (top) { - return this._getAbsoluteTransform(top); - } - // if no argument, we can cache the result - else { - return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale); - } - }, - _getAbsoluteScale: function(top) { - // this is special logic for caching with some shapes with shadow - var parent = this; - while(parent) { - if (parent._isUnderCache) { - top = parent; - } - parent = parent.getParent(); - } + getAbsoluteScale: function(top) { + // if using an argument, we can't cache the result. + if (top) { + return this._getAbsoluteScale(top); + } else { + // if no argument, we can cache the result + return this._getCache(ABSOLUTE_SCALE, this._getAbsoluteScale); + } + }, + _getAbsoluteScale: function(top) { + // this is special logic for caching with some shapes with shadow + var parent = this; + while (parent) { + if (parent._isUnderCache) { + top = parent; + } + parent = parent.getParent(); + } + var scaleX = 1, scaleY = 1; - var scaleX = 1, scaleY = 1; - - // start with stage and traverse downwards to self - this._eachAncestorReverse(function(node) { - scaleX *= node.scaleX(); - scaleY *= node.scaleY(); - }, top); - return { - x: scaleX, - y: scaleY - }; - }, - /** + // start with stage and traverse downwards to self + this._eachAncestorReverse(function(node) { + scaleX *= node.scaleX(); + scaleY *= node.scaleY(); + }, top); + return { + x: scaleX, + y: scaleY + }; + }, + /** * get transform of the node * @method * @memberof Konva.Node.prototype * @returns {Konva.Transform} */ - getTransform: function() { - return this._getCache(TRANSFORM, this._getTransform); - }, - _getTransform: function() { - var m = new Konva.Transform(), - x = this.getX(), - y = this.getY(), - rotation = Konva.getAngle(this.getRotation()), - scaleX = this.getScaleX(), - scaleY = this.getScaleY(), - skewX = this.getSkewX(), - skewY = this.getSkewY(), - offsetX = this.getOffsetX(), - offsetY = this.getOffsetY(); + getTransform: function() { + return this._getCache(TRANSFORM, this._getTransform); + }, + _getTransform: function() { + var m = new Konva.Transform(), + x = this.getX(), + y = this.getY(), + rotation = Konva.getAngle(this.getRotation()), + scaleX = this.getScaleX(), + scaleY = this.getScaleY(), + skewX = this.getSkewX(), + skewY = this.getSkewY(), + offsetX = this.getOffsetX(), + offsetY = this.getOffsetY(); - if(x !== 0 || y !== 0) { - m.translate(x, y); - } - if(rotation !== 0) { - m.rotate(rotation); - } - if(skewX !== 0 || skewY !== 0) { - m.skew(skewX, skewY); - } - if(scaleX !== 1 || scaleY !== 1) { - m.scale(scaleX, scaleY); - } - if(offsetX !== 0 || offsetY !== 0) { - m.translate(-1 * offsetX, -1 * offsetY); - } + if (x !== 0 || y !== 0) { + m.translate(x, y); + } + if (rotation !== 0) { + m.rotate(rotation); + } + if (skewX !== 0 || skewY !== 0) { + m.skew(skewX, skewY); + } + if (scaleX !== 1 || scaleY !== 1) { + m.scale(scaleX, scaleY); + } + if (offsetX !== 0 || offsetY !== 0) { + m.translate(-1 * offsetX, -1 * offsetY); + } - return m; - }, - /** + return m; + }, + /** * clone node. Returns a new Node instance with identical attributes. You can also override * the node properties with an object literal, enabling you to use an existing node as a template * for another node @@ -1501,68 +1505,76 @@ * x: 5 * }); */ - clone: function(obj) { - // instantiate new node - var attrs = Konva.Util.cloneObject(this.attrs), - key, allListeners, len, n, listener; - // filter black attrs - for (var i in CLONE_BLACK_LIST) { - var blockAttr = CLONE_BLACK_LIST[i]; - delete attrs[blockAttr]; - } - // apply attr overrides - for (key in obj) { - attrs[key] = obj[key]; - } + clone: function(obj) { + // instantiate new node + var attrs = Konva.Util.cloneObject(this.attrs), + key, + allListeners, + len, + n, + listener; + // filter black attrs + for (var i in CLONE_BLACK_LIST) { + var blockAttr = CLONE_BLACK_LIST[i]; + delete attrs[blockAttr]; + } + // apply attr overrides + for (key in obj) { + attrs[key] = obj[key]; + } - var node = new this.constructor(attrs); - // copy over listeners - for(key in this.eventListeners) { - allListeners = this.eventListeners[key]; - len = allListeners.length; - for(n = 0; n < len; n++) { - listener = allListeners[n]; - /* + var node = new this.constructor(attrs); + // copy over listeners + for (key in this.eventListeners) { + allListeners = this.eventListeners[key]; + len = allListeners.length; + for (n = 0; n < len; n++) { + listener = allListeners[n]; + /* * don't include konva namespaced listeners because * these are generated by the constructors */ - if(listener.name.indexOf(KONVA) < 0) { - // if listeners array doesn't exist, then create it - if(!node.eventListeners[key]) { - node.eventListeners[key] = []; - } - node.eventListeners[key].push(listener); - } - } + if (listener.name.indexOf(KONVA) < 0) { + // if listeners array doesn't exist, then create it + if (!node.eventListeners[key]) { + node.eventListeners[key] = []; } - return node; - }, - _toKonvaCanvas: function(config) { - config = config || {}; - - var stage = this.getStage(), - x = config.x || 0, - y = config.y || 0, - pixelRatio = config.pixelRatio || 1, - canvas = new Konva.SceneCanvas({ - width: config.width || this.getWidth() || (stage ? stage.getWidth() : 0), - height: config.height || this.getHeight() || (stage ? stage.getHeight() : 0), - pixelRatio: pixelRatio - }), - context = canvas.getContext(); - - context.save(); - - if(x || y) { - context.translate(-1 * x, -1 * y); + node.eventListeners[key].push(listener); } + } + } + return node; + }, + _toKonvaCanvas: function(config) { + config = config || {}; - this.drawScene(canvas); - context.restore(); + var stage = this.getStage(), + x = config.x || 0, + y = config.y || 0, + pixelRatio = config.pixelRatio || 1, + canvas = new Konva.SceneCanvas({ + width: config.width || + this.getWidth() || + (stage ? stage.getWidth() : 0), + height: config.height || + this.getHeight() || + (stage ? stage.getHeight() : 0), + pixelRatio: pixelRatio + }), + context = canvas.getContext(); - return canvas; - }, - /** + context.save(); + + if (x || y) { + context.translate(-1 * x, -1 * y); + } + + this.drawScene(canvas); + context.restore(); + + return canvas; + }, + /** * converts node into an canvas element. * @method * @memberof Konva.Node.prototype @@ -1576,10 +1588,10 @@ * @example * var canvas = node.toCanvas(); */ - toCanvas: function(config) { - return this._toKonvaCanvas(config)._canvas; - }, - /** + toCanvas: function(config) { + return this._toKonvaCanvas(config)._canvas; + }, + /** * Creates a composite data URL. If MIME type is not * specified, then "image/png" will result. For "image/jpeg", specify a quality * level as quality (range 0.0 - 1.0) @@ -1598,13 +1610,12 @@ * @paremt {Number} [config.pixelRatio] pixelRatio of ouput image url. Default is 1 * @returns {String} */ - toDataURL: function(config) { - config = config || {}; - var mimeType = config.mimeType || null, - quality = config.quality || null; - return this._toKonvaCanvas(config).toDataURL(mimeType, quality); - }, - /** + toDataURL: function(config) { + config = config || {}; + var mimeType = config.mimeType || null, quality = config.quality || null; + return this._toKonvaCanvas(config).toDataURL(mimeType, quality); + }, + /** * converts node into an image. Since the toImage * method is asynchronous, a callback is required. toImage is most commonly used * to cache complex drawings as an image so that they don't have to constantly be redrawn @@ -1629,121 +1640,125 @@ * } * }); */ - toImage: function(config) { - if (!config || !config.callback) { - throw 'callback required for toImage method config argument'; - } - Konva.Util._getImage(this.toDataURL(config), function(img) { - config.callback(img); - }); - }, - setSize: function(size) { - this.setWidth(size.width); - this.setHeight(size.height); - return this; - }, - getSize: function() { - return { - width: this.getWidth(), - height: this.getHeight() - }; - }, - getWidth: function() { - return this.attrs.width || 0; - }, - getHeight: function() { - return this.attrs.height || 0; - }, - /** + toImage: function(config) { + if (!config || !config.callback) { + throw 'callback required for toImage method config argument'; + } + Konva.Util._getImage(this.toDataURL(config), function(img) { + config.callback(img); + }); + }, + setSize: function(size) { + this.setWidth(size.width); + this.setHeight(size.height); + return this; + }, + getSize: function() { + return { + width: this.getWidth(), + height: this.getHeight() + }; + }, + getWidth: function() { + return this.attrs.width || 0; + }, + getHeight: function() { + return this.attrs.height || 0; + }, + /** * get class name, which may return Stage, Layer, Group, or shape class names like Rect, Circle, Text, etc. * @method * @memberof Konva.Node.prototype * @returns {String} */ - getClassName: function() { - return this.className || this.nodeType; - }, - /** + getClassName: function() { + return this.className || this.nodeType; + }, + /** * get the node type, which may return Stage, Layer, Group, or Node * @method * @memberof Konva.Node.prototype * @returns {String} */ - getType: function() { - return this.nodeType; - }, - getDragDistance: function() { - // compare with undefined because we need to track 0 value - if (this.attrs.dragDistance !== undefined) { - return this.attrs.dragDistance; - } else if (this.parent) { - return this.parent.getDragDistance(); - } else { - return Konva.dragDistance; - } - }, - _get: function(selector) { - return this.className === selector || this.nodeType === selector ? [this] : []; - }, - _off: function(type, name) { - var evtListeners = this.eventListeners[type], - i, evtName; + getType: function() { + return this.nodeType; + }, + getDragDistance: function() { + // compare with undefined because we need to track 0 value + if (this.attrs.dragDistance !== undefined) { + return this.attrs.dragDistance; + } else if (this.parent) { + return this.parent.getDragDistance(); + } else { + return Konva.dragDistance; + } + }, + _get: function(selector) { + return this.className === selector || this.nodeType === selector + ? [this] + : []; + }, + _off: function(type, name) { + var evtListeners = this.eventListeners[type], i, evtName; - for(i = 0; i < evtListeners.length; i++) { - evtName = evtListeners[i].name; - // the following two conditions must be true in order to remove a handler: - // 1) the current event name cannot be konva unless the event name is konva - // this enables developers to force remove a konva specific listener for whatever reason - // 2) an event name is not specified, or if one is specified, it matches the current event name - if((evtName !== 'konva' || name === 'konva') && (!name || evtName === name)) { - evtListeners.splice(i, 1); - if(evtListeners.length === 0) { - delete this.eventListeners[type]; - break; - } - i--; - } - } - }, - _fireChangeEvent: function(attr, oldVal, newVal) { - this._fire(attr + CHANGE, { - oldVal: oldVal, - newVal: newVal - }); - }, - setId: function(id) { - var oldId = this.getId(); + for (i = 0; i < evtListeners.length; i++) { + evtName = evtListeners[i].name; + // the following two conditions must be true in order to remove a handler: + // 1) the current event name cannot be konva unless the event name is konva + // this enables developers to force remove a konva specific listener for whatever reason + // 2) an event name is not specified, or if one is specified, it matches the current event name + if ( + (evtName !== 'konva' || name === 'konva') && + (!name || evtName === name) + ) { + evtListeners.splice(i, 1); + if (evtListeners.length === 0) { + delete this.eventListeners[type]; + break; + } + i--; + } + } + }, + _fireChangeEvent: function(attr, oldVal, newVal) { + this._fire(attr + CHANGE, { + oldVal: oldVal, + newVal: newVal + }); + }, + setId: function(id) { + var oldId = this.getId(); - Konva._removeId(oldId); - Konva._addId(this, id); - this._setAttr(ID, id); - return this; - }, - setName: function(name) { - var oldNames = (this.getName() || '').split(/\s/g); - var newNames = (name || '').split(/\s/g); - var subname, i; - // remove all subnames - for(i = 0; i < oldNames.length; i++) { - subname = oldNames[i]; - if ((newNames.indexOf(subname)) === -1 && subname) { - Konva._removeName(subname, this._id); - } - } + Konva._removeId(oldId); + Konva._addId(this, id); + this._setAttr(ID, id); + return this; + }, + setName: function(name) { + var oldNames = (this.getName() || '').split(/\s/g); + var newNames = (name || '').split(/\s/g); + var subname, i; + // remove all subnames + for (i = 0; i < oldNames.length; i++) { + subname = oldNames[i]; + if (newNames.indexOf(subname) === -1 && subname) { + Konva._removeName(subname, this._id); + } + } - // add new names - for(i = 0; i < newNames.length; i++) { - subname = newNames[i]; - if ((oldNames.indexOf(subname) === -1) && subname) { - Konva._addName(this, subname); - } - } + // add new names + for (i = 0; i < newNames.length; i++) { + subname = newNames[i]; + if (oldNames.indexOf(subname) === -1 && subname) { + Konva._addName(this, subname); + } + } - this._setAttr(NAME, name); - return this; - }, - // naming methods - /** + this._setAttr(NAME, name); + return this; + }, + // naming methods + /** * add name to node * @method * @memberof Konva.Node.prototype @@ -1754,15 +1769,15 @@ * node.addName('selected'); * node.name(); // return 'red selected' */ - addName: function(name) { - if (!this.hasName(name)) { - var oldName = this.name(); - var newName = oldName ? (oldName + ' ' + name) : name; - this.setName(newName); - } - return this; - }, - /** + addName: function(name) { + if (!this.hasName(name)) { + var oldName = this.name(); + var newName = oldName ? oldName + ' ' + name : name; + this.setName(newName); + } + return this; + }, + /** * check is node has name * @method * @memberof Konva.Node.prototype @@ -1773,11 +1788,11 @@ * node.hasName('red'); // return true * node.hasName('selected'); // return false */ - hasName: function(name) { - var names = (this.name() || '').split(/\s/g); - return names.indexOf(name) !== -1; - }, - /** + hasName: function(name) { + var names = (this.name() || '').split(/\s/g); + return names.indexOf(name) !== -1; + }, + /** * remove name from node * @method * @memberof Konva.Node.prototype @@ -1789,16 +1804,16 @@ * node.hasName('selected'); // return false * node.name(); // return 'red' */ - removeName: function(name) { - var names = (this.name() || '').split(/\s/g); - var index = names.indexOf(name); - if (index !== -1) { - names.splice(index, 1); - this.setName(names.join(' ')); - } - return this; - }, - /** + removeName: function(name) { + var names = (this.name() || '').split(/\s/g); + var index = names.indexOf(name); + if (index !== -1) { + names.splice(index, 1); + this.setName(names.join(' ')); + } + return this; + }, + /** * set attr * @method * @memberof Konva.Node.prototype @@ -1808,104 +1823,122 @@ * @example * node.setAttr('x', 5); */ - setAttr: function(attr, val) { - var method = SET + Konva.Util._capitalize(attr), - func = this[method]; + setAttr: function(attr, val) { + var method = SET + Konva.Util._capitalize(attr), func = this[method]; - if(Konva.Util._isFunction(func)) { - func.call(this, val); - } - // otherwise set directly - else { - this._setAttr(attr, val); - } - return this; - }, - _setAttr: function(key, val) { - var oldVal; - oldVal = this.attrs[key]; - if (oldVal === val) { - return; - } - if (val === undefined || val === null) { - delete this.attrs[key]; - } else { - this.attrs[key] = val; - } - this._fireChangeEvent(key, oldVal, val); - }, - _setComponentAttr: function(key, component, val) { - var oldVal; - if(val !== undefined) { - oldVal = this.attrs[key]; + if (Konva.Util._isFunction(func)) { + func.call(this, val); + } else { + // otherwise set directly + this._setAttr(attr, val); + } + return this; + }, + _setAttr: function(key, val) { + var oldVal; + oldVal = this.attrs[key]; + if (oldVal === val) { + return; + } + if (val === undefined || val === null) { + delete this.attrs[key]; + } else { + this.attrs[key] = val; + } + this._fireChangeEvent(key, oldVal, val); + }, + _setComponentAttr: function(key, component, val) { + var oldVal; + if (val !== undefined) { + oldVal = this.attrs[key]; - if (!oldVal) { - // set value to default value using getAttr - this.attrs[key] = this.getAttr(key); - } + if (!oldVal) { + // set value to default value using getAttr + this.attrs[key] = this.getAttr(key); + } - this.attrs[key][component] = val; - this._fireChangeEvent(key, oldVal, val); - } - }, - _fireAndBubble: function(eventType, evt, compareShape) { - var okayToRun = true; + this.attrs[key][component] = val; + this._fireChangeEvent(key, oldVal, val); + } + }, + _fireAndBubble: function(eventType, evt, compareShape) { + var okayToRun = true; - if(evt && this.nodeType === SHAPE) { - evt.target = this; - } + if (evt && this.nodeType === SHAPE) { + evt.target = this; + } - if(eventType === MOUSEENTER && compareShape && (this._id === compareShape._id || (this.isAncestorOf && this.isAncestorOf(compareShape)))) { - okayToRun = false; - } - else if(eventType === MOUSELEAVE && compareShape && (this._id === compareShape._id || (this.isAncestorOf && this.isAncestorOf(compareShape)))) { - okayToRun = false; - } - if(okayToRun) { - this._fire(eventType, evt); + if ( + eventType === MOUSEENTER && + compareShape && + (this._id === compareShape._id || + (this.isAncestorOf && this.isAncestorOf(compareShape))) + ) { + okayToRun = false; + } else if ( + eventType === MOUSELEAVE && + compareShape && + (this._id === compareShape._id || + (this.isAncestorOf && this.isAncestorOf(compareShape))) + ) { + okayToRun = false; + } + if (okayToRun) { + this._fire(eventType, evt); - // simulate event bubbling - var stopBubble = - (eventType === MOUSEENTER || eventType === MOUSELEAVE) && - ((compareShape && compareShape.isAncestorOf && compareShape.isAncestorOf(this) && !compareShape.isAncestorOf(this.parent))); - if((evt && !evt.cancelBubble || !evt) && this.parent && this.parent.isListening() && (!stopBubble)) { - if (compareShape && compareShape.parent) { - this._fireAndBubble.call(this.parent, eventType, evt, compareShape.parent); - } - else { - this._fireAndBubble.call(this.parent, eventType, evt); - } - } - } - }, - _fire: function(eventType, evt) { - var events = this.eventListeners[eventType], - i; + // simulate event bubbling + var stopBubble = + (eventType === MOUSEENTER || eventType === MOUSELEAVE) && + (compareShape && + compareShape.isAncestorOf && + compareShape.isAncestorOf(this) && + !compareShape.isAncestorOf(this.parent)); + if ( + ((evt && !evt.cancelBubble) || !evt) && + this.parent && + this.parent.isListening() && + !stopBubble + ) { + if (compareShape && compareShape.parent) { + this._fireAndBubble.call( + this.parent, + eventType, + evt, + compareShape.parent + ); + } else { + this._fireAndBubble.call(this.parent, eventType, evt); + } + } + } + }, + _fire: function(eventType, evt) { + var events = this.eventListeners[eventType], i; - evt = evt || {}; - evt.currentTarget = this; - evt.type = eventType; + evt = evt || {}; + evt.currentTarget = this; + evt.type = eventType; - if (events) { - for(i = 0; i < events.length; i++) { - events[i].handler.call(this, evt); - } - } - }, - /** + if (events) { + for (i = 0; i < events.length; i++) { + events[i].handler.call(this, evt); + } + } + }, + /** * draw both scene and hit graphs. If the node being drawn is the stage, all of the layers will be cleared and redrawn * @method * @memberof Konva.Node.prototype * @returns {Konva.Node} */ - draw: function() { - this.drawScene(); - this.drawHit(); - return this; - } - }); + draw: function() { + this.drawScene(); + this.drawHit(); + return this; + } + }); - /** + /** * create node with JSON string or an Object. De-serializtion does not generate custom * shape drawing functions, images, or event handlers (this would make the * serialized object huge). If your app uses custom shapes, images, and @@ -1918,38 +1951,39 @@ * @param {Element} [container] optional container dom element used only if you're * creating a stage node */ - Konva.Node.create = function(data, container) { - if (Konva.Util._isString(data)) { - data = JSON.parse(data); - } - return this._createNode(data, container); - }; - Konva.Node._createNode = function(obj, container) { - var className = Konva.Node.prototype.getClassName.call(obj), - children = obj.children, - no, len, n; + Konva.Node.create = function(data, container) { + if (Konva.Util._isString(data)) { + data = JSON.parse(data); + } + return this._createNode(data, container); + }; + Konva.Node._createNode = function(obj, container) { + var className = Konva.Node.prototype.getClassName.call(obj), + children = obj.children, + no, + len, + n; - // if container was passed in, add it to attrs - if(container) { - obj.attrs.container = container; - } + // if container was passed in, add it to attrs + if (container) { + obj.attrs.container = container; + } - no = new Konva[className](obj.attrs); - if(children) { - len = children.length; - for(n = 0; n < len; n++) { - no.add(this._createNode(children[n])); - } - } + no = new Konva[className](obj.attrs); + if (children) { + len = children.length; + for (n = 0; n < len; n++) { + no.add(this._createNode(children[n])); + } + } - return no; - }; + return no; + }; + // =========================== add getters setters =========================== - // =========================== add getters setters =========================== - - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position'); - /** + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'position'); + /** * get/set node position relative to parent * @name position * @method @@ -1969,9 +2003,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'x', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'x', 0); - /** + /** * get/set x position * @name x * @method @@ -1986,9 +2020,9 @@ * node.x(5); */ - Konva.Factory.addGetterSetter(Konva.Node, 'y', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'y', 0); - /** + /** * get/set y position * @name y * @method @@ -2003,9 +2037,29 @@ * node.y(5); */ - Konva.Factory.addGetterSetter(Konva.Node, 'opacity', 1); + Konva.Factory.addGetterSetter( + Konva.Node, + 'globalCompositeOperation', + 'source-over' + ); - /** + /** + * get/set globalCompositeOperation of a shape + * @name globalCompositeOperation + * @method + * @memberof Konva.Node.prototype + * @param {Number} blur + * @returns {Number} + * @example + * // get shadow blur + * var globalCompositeOperation = shape.globalCompositeOperation(); + * + * // set shadow blur + * shape.globalCompositeOperation('source-in'); + */ + Konva.Factory.addGetterSetter(Konva.Node, 'opacity', 1); + + /** * get/set opacity. Opacity values range from 0 to 1. * A node with an opacity of 0 is fully transparent, and a node * with an opacity of 1 is fully opaque @@ -2022,10 +2076,10 @@ * node.opacity(0.5); */ - Konva.Factory.addGetter(Konva.Node, 'name'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name'); + Konva.Factory.addGetter(Konva.Node, 'name'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'name'); - /** + /** * get/set name * @name name * @method @@ -2043,10 +2097,10 @@ * node.name('foo bar'); */ - Konva.Factory.addGetter(Konva.Node, 'id'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id'); + Konva.Factory.addGetter(Konva.Node, 'id'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'id'); - /** + /** * get/set id. Id is global for whole page. * @name id * @method @@ -2061,9 +2115,9 @@ * node.id('foo'); */ - Konva.Factory.addGetterSetter(Konva.Node, 'rotation', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'rotation', 0); - /** + /** * get/set rotation in degrees * @name rotation * @method @@ -2078,9 +2132,9 @@ * node.rotation(45); */ - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'scale', ['x', 'y']); - /** + /** * get/set scale * @name scale * @param {Object} scale @@ -2100,9 +2154,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'scaleX', 1); + Konva.Factory.addGetterSetter(Konva.Node, 'scaleX', 1); - /** + /** * get/set scale x * @name scaleX * @param {Number} x @@ -2117,9 +2171,9 @@ * node.scaleX(2); */ - Konva.Factory.addGetterSetter(Konva.Node, 'scaleY', 1); + Konva.Factory.addGetterSetter(Konva.Node, 'scaleY', 1); - /** + /** * get/set scale y * @name scaleY * @param {Number} y @@ -2134,9 +2188,9 @@ * node.scaleY(2); */ - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'skew', ['x', 'y']); - /** + /** * get/set skew * @name skew * @param {Object} skew @@ -2156,9 +2210,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'skewX', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'skewX', 0); - /** + /** * get/set skew x * @name skewX * @param {Number} x @@ -2173,9 +2227,9 @@ * node.skewX(3); */ - Konva.Factory.addGetterSetter(Konva.Node, 'skewY', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'skewY', 0); - /** + /** * get/set skew y * @name skewY * @param {Number} y @@ -2190,9 +2244,9 @@ * node.skewY(3); */ - Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Node, 'offset', ['x', 'y']); - /** + /** * get/set offset. Offsets the default position and rotation point * @method * @memberof Konva.Node.prototype @@ -2211,9 +2265,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Node, 'offsetX', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'offsetX', 0); - /** + /** * get/set offset x * @name offsetX * @method @@ -2228,9 +2282,9 @@ * node.offsetX(3); */ - Konva.Factory.addGetterSetter(Konva.Node, 'offsetY', 0); + Konva.Factory.addGetterSetter(Konva.Node, 'offsetY', 0); - /** + /** * get/set offset y * @name offsetY * @method @@ -2245,10 +2299,10 @@ * node.offsetY(3); */ - Konva.Factory.addSetter(Konva.Node, 'dragDistance'); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance'); + Konva.Factory.addSetter(Konva.Node, 'dragDistance'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'dragDistance'); - /** + /** * get/set drag distance * @name dragDistance * @method @@ -2266,10 +2320,9 @@ * Konva.dragDistance = 3; */ - - Konva.Factory.addSetter(Konva.Node, 'width', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width'); - /** + Konva.Factory.addSetter(Konva.Node, 'width', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'width'); + /** * get/set width * @name width * @method @@ -2284,9 +2337,9 @@ * node.width(100); */ - Konva.Factory.addSetter(Konva.Node, 'height', 0); - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height'); - /** + Konva.Factory.addSetter(Konva.Node, 'height', 0); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'height'); + /** * get/set height * @name height * @method @@ -2301,8 +2354,8 @@ * node.height(100); */ - Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit'); - /** + Konva.Factory.addGetterSetter(Konva.Node, 'listening', 'inherit'); + /** * get/set listenig attr. If you need to determine if a node is listening or not * by taking into account its parents, use the isListening() method * @name listening @@ -2324,9 +2377,7 @@ * node.listening('inherit'); */ - - - /** + /** * get/set preventDefault * By default all shapes will prevent default behaviour * of a browser on a pointer move or tap. @@ -2346,10 +2397,15 @@ * shape.preventDefault(false); */ - Konva.Factory.addGetterSetter(Konva.Node, 'preventDefault', true); + Konva.Factory.addGetterSetter(Konva.Node, 'preventDefault', true); - Konva.Factory.addGetterSetter(Konva.Node, 'filters', undefined, function(val) {this._filterUpToDate = false; return val; }); - /** + Konva.Factory.addGetterSetter(Konva.Node, 'filters', undefined, function( + val + ) { + this._filterUpToDate = false; + return val; + }); + /** * get/set filters. Filters are applied to cached canvases * @name filters * @method @@ -2373,8 +2429,8 @@ * ]); */ - Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit'); - /** + Konva.Factory.addGetterSetter(Konva.Node, 'visible', 'inherit'); + /** * get/set visible attr. Can be "inherit", true, or false. The default is "inherit". * If you need to determine if a node is visible or not * by taking into account its parents, use the isVisible() method @@ -2397,9 +2453,9 @@ * node.visible('inherit'); */ - Konva.Factory.addGetterSetter(Konva.Node, 'transformsEnabled', 'all'); + Konva.Factory.addGetterSetter(Konva.Node, 'transformsEnabled', 'all'); - /** + /** * get/set transforms that are enabled. Can be "all", "none", or "position". The default * is "all" * @name transformsEnabled @@ -2415,9 +2471,7 @@ * node.transformsEnabled('all'); */ - - - /** + /** * get/set node size * @name size * @method @@ -2438,13 +2492,13 @@ * height: 200 * }); */ - Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size'); + Konva.Factory.addOverloadedGetterSetter(Konva.Node, 'size'); - Konva.Factory.backCompat(Konva.Node, { - rotateDeg: 'rotate', - setRotationDeg: 'setRotation', - getRotationDeg: 'getRotation' - }); + Konva.Factory.backCompat(Konva.Node, { + rotateDeg: 'rotate', + setRotationDeg: 'setRotation', + getRotationDeg: 'getRotation' + }); - Konva.Collection.mapMethods(Konva.Node); + Konva.Collection.mapMethods(Konva.Node); })(Konva); diff --git a/src/Shape.js b/src/Shape.js index 4de5d9a0..b8278835 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -1,30 +1,30 @@ (function(Konva) { - 'use strict'; - var HAS_SHADOW = 'hasShadow'; - var SHADOW_RGBA = 'shadowRGBA'; + 'use strict'; + var HAS_SHADOW = 'hasShadow'; + var SHADOW_RGBA = 'shadowRGBA'; - function _fillFunc(context) { - context.fill(); - } - function _strokeFunc(context) { - context.stroke(); - } - function _fillFuncHit(context) { - context.fill(); - } - function _strokeFuncHit(context) { - context.stroke(); - } + function _fillFunc(context) { + context.fill(); + } + function _strokeFunc(context) { + context.stroke(); + } + function _fillFuncHit(context) { + context.fill(); + } + function _strokeFuncHit(context) { + context.stroke(); + } - function _clearHasShadowCache() { - this._clearCache(HAS_SHADOW); - } + function _clearHasShadowCache() { + this._clearCache(HAS_SHADOW); + } - function _clearGetShadowRGBACache() { - this._clearCache(SHADOW_RGBA); - } + function _clearGetShadowRGBACache() { + this._clearCache(SHADOW_RGBA); + } - /** + /** * Shape constructor. Shapes are primitive objects such as rectangles, * circles, text, lines, etc. * @constructor @@ -49,103 +49,125 @@ * } *}); */ - Konva.Shape = function(config) { - this.__init(config); - }; + Konva.Shape = function(config) { + this.__init(config); + }; - Konva.Util.addMethods(Konva.Shape, { - __init: function(config) { - this.nodeType = 'Shape'; - this._fillFunc = _fillFunc; - this._strokeFunc = _strokeFunc; - this._fillFuncHit = _fillFuncHit; - this._strokeFuncHit = _strokeFuncHit; + Konva.Util.addMethods(Konva.Shape, { + __init: function(config) { + this.nodeType = 'Shape'; + this._fillFunc = _fillFunc; + this._strokeFunc = _strokeFunc; + this._fillFuncHit = _fillFuncHit; + this._strokeFuncHit = _strokeFuncHit; - // set colorKey - var shapes = Konva.shapes; - var key; + // set colorKey + var shapes = Konva.shapes; + var key; - while(true) { - key = Konva.Util.getRandomColor(); - if(key && !( key in shapes)) { - break; - } - } + while (true) { + key = Konva.Util.getRandomColor(); + if (key && !(key in shapes)) { + break; + } + } - this.colorKey = key; - shapes[key] = this; + this.colorKey = key; + shapes[key] = this; - // call super constructor - Konva.Node.call(this, config); + // call super constructor + Konva.Node.call(this, config); - this.on('shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearHasShadowCache); + this.on( + 'shadowColorChange.konva shadowBlurChange.konva shadowOffsetChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearHasShadowCache + ); - this.on('shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', _clearGetShadowRGBACache); - }, - hasChildren: function() { - return false; - }, - getChildren: function() { - return []; - }, - /** + this.on( + 'shadowColorChange.konva shadowOpacityChange.konva shadowEnabledChange.konva', + _clearGetShadowRGBACache + ); + }, + hasChildren: function() { + return false; + }, + getChildren: function() { + return []; + }, + /** * get canvas context tied to the layer * @method * @memberof Konva.Shape.prototype * @returns {Konva.Context} */ - getContext: function() { - return this.getLayer().getContext(); - }, - /** + getContext: function() { + return this.getLayer().getContext(); + }, + /** * get canvas renderer tied to the layer. Note that this returns a canvas renderer, not a canvas element * @method * @memberof Konva.Shape.prototype * @returns {Konva.Canvas} */ - getCanvas: function() { - return this.getLayer().getCanvas(); - }, - /** + getCanvas: function() { + return this.getLayer().getCanvas(); + }, + /** * returns whether or not a shadow will be rendered * @method * @memberof Konva.Shape.prototype * @returns {Boolean} */ - hasShadow: function() { - return this._getCache(HAS_SHADOW, this._hasShadow); - }, - _hasShadow: function() { - return this.getShadowEnabled() && (this.getShadowOpacity() !== 0 && !!(this.getShadowColor() || this.getShadowBlur() || this.getShadowOffsetX() || this.getShadowOffsetY())); - }, - getShadowRGBA: function() { - return this._getCache(SHADOW_RGBA, this._getShadowRGBA); - }, - _getShadowRGBA: function() { - if (this.hasShadow()) { - var rgba = Konva.Util.colorToRGBA(this.shadowColor()); - return 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + (rgba.a * (this.getShadowOpacity() || 1)) + ')'; - } - }, - /** + hasShadow: function() { + return this._getCache(HAS_SHADOW, this._hasShadow); + }, + _hasShadow: function() { + return this.getShadowEnabled() && + (this.getShadowOpacity() !== 0 && + !!(this.getShadowColor() || + this.getShadowBlur() || + this.getShadowOffsetX() || + this.getShadowOffsetY())); + }, + getShadowRGBA: function() { + return this._getCache(SHADOW_RGBA, this._getShadowRGBA); + }, + _getShadowRGBA: function() { + if (this.hasShadow()) { + var rgba = Konva.Util.colorToRGBA(this.shadowColor()); + return 'rgba(' + + rgba.r + + ',' + + rgba.g + + ',' + + rgba.b + + ',' + + rgba.a * (this.getShadowOpacity() || 1) + + ')'; + } + }, + /** * returns whether or not the shape will be filled * @method * @memberof Konva.Shape.prototype * @returns {Boolean} */ - hasFill: function() { - return !!(this.getFill() || this.getFillPatternImage() || this.getFillLinearGradientColorStops() || this.getFillRadialGradientColorStops()); - }, - /** + hasFill: function() { + return !!(this.getFill() || + this.getFillPatternImage() || + this.getFillLinearGradientColorStops() || + this.getFillRadialGradientColorStops()); + }, + /** * returns whether or not the shape will be stroked * @method * @memberof Konva.Shape.prototype * @returns {Boolean} */ - hasStroke: function() { - return this.strokeEnabled() && !!(this.stroke()); - }, - /** + hasStroke: function() { + return this.strokeEnabled() && !!this.stroke(); + }, + /** * determines if point is in the shape, regardless if other shapes are on top of it. Note: because * this method clears a temporary canvas and then redraws the shape, it performs very poorly if executed many times * consecutively. Please use the {@link Konva.Stage#getIntersection} method if at all possible @@ -157,27 +179,40 @@ * @param {Number} point.y * @returns {Boolean} */ - intersects: function(point) { - var stage = this.getStage(), - bufferHitCanvas = stage.bufferHitCanvas, - p; + intersects: function(point) { + var stage = this.getStage(), bufferHitCanvas = stage.bufferHitCanvas, p; - bufferHitCanvas.getContext().clear(); - this.drawScene(bufferHitCanvas); - p = bufferHitCanvas.context.getImageData(Math.round(point.x), Math.round(point.y), 1, 1).data; - return p[3] > 0; - }, - // extends Node.prototype.destroy - destroy: function() { - Konva.Node.prototype.destroy.call(this); - delete Konva.shapes[this.colorKey]; - return this; - }, - _useBufferCanvas: function(caching) { - return !caching && (this.perfectDrawEnabled() && (this.getAbsoluteOpacity() !== 1) && this.hasFill() && this.hasStroke() && this.getStage()) || - (this.perfectDrawEnabled() && this.hasShadow() && (this.getAbsoluteOpacity() !== 1) && this.hasFill() && this.hasStroke() && this.getStage()); - }, - /** + bufferHitCanvas.getContext().clear(); + this.drawHit(bufferHitCanvas); + p = bufferHitCanvas.context.getImageData( + Math.round(point.x), + Math.round(point.y), + 1, + 1 + ).data; + return p[3] > 0; + }, + // extends Node.prototype.destroy + destroy: function() { + Konva.Node.prototype.destroy.call(this); + delete Konva.shapes[this.colorKey]; + return this; + }, + _useBufferCanvas: function(caching) { + return (!caching && + (this.perfectDrawEnabled() && + this.getAbsoluteOpacity() !== 1 && + this.hasFill() && + this.hasStroke() && + this.getStage())) || + (this.perfectDrawEnabled() && + this.hasShadow() && + this.getAbsoluteOpacity() !== 1 && + this.hasFill() && + this.hasStroke() && + this.getStage()); + }, + /** * return self rectangle (x, y, width, height) of shape. * This method are not taken into account transformation and styles. * @method @@ -189,193 +224,217 @@ * circle.getSelfRect(); // return {x: - circle.width() / 2, y: - circle.height() / 2, width:circle.width(), height:circle.height()} * */ - getSelfRect: function() { - var size = this.getSize(); - return { - x: this._centroid ? Math.round(-size.width / 2) : 0, - y: this._centroid ? Math.round(-size.height / 2) : 0, - width: size.width, - height: size.height - }; - }, - getClientRect: function(skipTransform) { - var fillRect = this.getSelfRect(); + getSelfRect: function() { + var size = this.getSize(); + return { + x: this._centroid ? Math.round((-size.width) / 2) : 0, + y: this._centroid ? Math.round((-size.height) / 2) : 0, + width: size.width, + height: size.height + }; + }, + getClientRect: function(skipTransform) { + var fillRect = this.getSelfRect(); - var strokeWidth = (this.hasStroke() && this.strokeWidth()) || 0; - var fillAndStrokeWidth = fillRect.width + strokeWidth; - var fillAndStrokeHeight = fillRect.height + strokeWidth; + var strokeWidth = (this.hasStroke() && this.strokeWidth()) || 0; + var fillAndStrokeWidth = fillRect.width + strokeWidth; + var fillAndStrokeHeight = fillRect.height + strokeWidth; - var shadowOffsetX = this.hasShadow() ? this.shadowOffsetX() : 0; - var shadowOffsetY = this.hasShadow() ? this.shadowOffsetY() : 0; + var shadowOffsetX = this.hasShadow() ? this.shadowOffsetX() : 0; + var shadowOffsetY = this.hasShadow() ? this.shadowOffsetY() : 0; - var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); - var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); + var preWidth = fillAndStrokeWidth + Math.abs(shadowOffsetX); + var preHeight = fillAndStrokeHeight + Math.abs(shadowOffsetY); - var blurRadius = (this.hasShadow() && this.shadowBlur() || 0); + var blurRadius = (this.hasShadow() && this.shadowBlur()) || 0; - var width = preWidth + blurRadius * 2; - var height = preHeight + blurRadius * 2; + var width = preWidth + blurRadius * 2; + var height = preHeight + blurRadius * 2; - // if stroke, for example = 3 - // we need to set x to 1.5, but after Math.round it will be 2 - // as we have additional offset we need to increase width and height by 1 pixel - var roundingOffset = 0; - if (Math.round(strokeWidth / 2) !== strokeWidth / 2) { - roundingOffset = 1; - } - var rect = { - width: width + roundingOffset, - height: height + roundingOffset, - x: -Math.round(strokeWidth / 2 + blurRadius) + Math.min(shadowOffsetX, 0) + fillRect.x, - y: -Math.round(strokeWidth / 2 + blurRadius) + Math.min(shadowOffsetY, 0) + fillRect.y - }; - if (!skipTransform) { - return this._transformedRect(rect); - } - return rect; - }, - drawScene: function(can, top, caching, skipBuffer) { - var layer = this.getLayer(), - canvas = can || layer.getCanvas(), - context = canvas.getContext(), - cachedCanvas = this._cache.canvas, - drawFunc = this.sceneFunc(), - hasShadow = this.hasShadow(), - hasStroke = this.hasStroke(), - stage, bufferCanvas, bufferContext; + // if stroke, for example = 3 + // we need to set x to 1.5, but after Math.round it will be 2 + // as we have additional offset we need to increase width and height by 1 pixel + var roundingOffset = 0; + if (Math.round(strokeWidth / 2) !== strokeWidth / 2) { + roundingOffset = 1; + } + var rect = { + width: width + roundingOffset, + height: height + roundingOffset, + x: -Math.round(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetX, 0) + + fillRect.x, + y: -Math.round(strokeWidth / 2 + blurRadius) + + Math.min(shadowOffsetY, 0) + + fillRect.y + }; + if (!skipTransform) { + return this._transformedRect(rect); + } + return rect; + }, + drawScene: function(can, top, caching, skipBuffer) { + var layer = this.getLayer(), + canvas = can || layer.getCanvas(), + context = canvas.getContext(), + cachedCanvas = this._cache.canvas, + drawFunc = this.sceneFunc(), + hasShadow = this.hasShadow(), + hasStroke = this.hasStroke(), + stage, + bufferCanvas, + bufferContext; - if(!this.isVisible()) { - return this; - } - if (cachedCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedSceneCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - // if buffer canvas is needed - if (this._useBufferCanvas(caching) && !skipBuffer) { - stage = this.getStage(); - bufferCanvas = stage.bufferCanvas; - bufferContext = bufferCanvas.getContext(); - bufferContext.clear(); - bufferContext.save(); - bufferContext._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - if (!caching) { - if (layer) { - layer._applyTransform(this, bufferContext, top); - } else { - var m = this.getAbsoluteTransform(top).getMatrix(); - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); - } - } + if (!this.isVisible()) { + return this; + } + if (cachedCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedSceneCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + // if buffer canvas is needed + if (this._useBufferCanvas(caching) && !skipBuffer) { + stage = this.getStage(); + bufferCanvas = stage.bufferCanvas; + bufferContext = bufferCanvas.getContext(); + bufferContext.clear(); + bufferContext.save(); + bufferContext._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + if (!caching) { + if (layer) { + layer._applyTransform(this, bufferContext, top); + } else { + var m = this.getAbsoluteTransform(top).getMatrix(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + } + } - drawFunc.call(this, bufferContext); - bufferContext.restore(); + drawFunc.call(this, bufferContext); + bufferContext.restore(); - var ratio = bufferCanvas.pixelRatio; - if (hasShadow && !canvas.hitCanvas) { - context.save(); - context._applyShadow(this); - context._applyOpacity(this); - context.drawImage(bufferCanvas._canvas, 0, 0, bufferCanvas.width / ratio, bufferCanvas.height / ratio); - context.restore(); - } else { - context._applyOpacity(this); - context.drawImage(bufferCanvas._canvas, 0, 0, bufferCanvas.width / ratio, bufferCanvas.height / ratio); - } - } - // if buffer canvas is not needed - else { - context._applyLineJoin(this); - // layer might be undefined if we are using cache before adding to layer - if (!caching) { - if (layer) { - layer._applyTransform(this, context, top); - } else { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - } + var ratio = bufferCanvas.pixelRatio; + if (hasShadow && !canvas.hitCanvas) { + context.save(); - if (hasShadow && hasStroke && !canvas.hitCanvas) { - context.save(); - // apply shadow - if (!caching) { - context._applyOpacity(this); - } - context._applyShadow(this); - drawFunc.call(this, context); - context.restore(); - // if shape has stroke we need to redraw shape - // otherwise we will see a shadow under stroke (and over fill) - // but I think this is unexpected behavior - if (this.hasFill() && this.getShadowForStrokeEnabled()) { - drawFunc.call(this, context); - } - } else if (hasShadow && !canvas.hitCanvas) { - context.save(); - if (!caching) { - context._applyOpacity(this); - } - context._applyShadow(this); - drawFunc.call(this, context); - context.restore(); - } else { - if (!caching) { - context._applyOpacity(this); - } - drawFunc.call(this, context); - } - } - context.restore(); - return this; - }, - drawHit: function(can, top, caching) { - var layer = this.getLayer(), - canvas = can || layer.hitCanvas, - context = canvas.getContext(), - drawFunc = this.hitFunc() || this.sceneFunc(), - cachedCanvas = this._cache.canvas, - cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + context._applyShadow(this); + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.drawImage( + bufferCanvas._canvas, + 0, + 0, + bufferCanvas.width / ratio, + bufferCanvas.height / ratio + ); + context.restore(); + } else { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + context.drawImage( + bufferCanvas._canvas, + 0, + 0, + bufferCanvas.width / ratio, + bufferCanvas.height / ratio + ); + } + } else { + // if buffer canvas is not needed + context._applyLineJoin(this); + // layer might be undefined if we are using cache before adding to layer + if (!caching) { + if (layer) { + layer._applyTransform(this, context, top); + } else { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + } - if(!this.shouldDrawHit(canvas)) { - return this; - } - if (layer) { - layer.clearHitCache(); - } - if (cachedHitCanvas) { - context.save(); - layer._applyTransform(this, context, top); - this._drawCachedHitCanvas(context); - context.restore(); - return this; - } - if (!drawFunc) { - return this; - } - context.save(); - context._applyLineJoin(this); - if (!caching) { - if (layer) { - layer._applyTransform(this, context, top); - } else { - var o = this.getAbsoluteTransform(top).getMatrix(); - context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); - } - } + if (hasShadow && hasStroke && !canvas.hitCanvas) { + context.save(); + // apply shadow + if (!caching) { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + context._applyShadow(this); + + drawFunc.call(this, context); + context.restore(); + // if shape has stroke we need to redraw shape + // otherwise we will see a shadow under stroke (and over fill) + // but I think this is unexpected behavior + if (this.hasFill() && this.getShadowForStrokeEnabled()) { drawFunc.call(this, context); - context.restore(); - return this; - }, - /** + } + } else if (hasShadow && !canvas.hitCanvas) { + context.save(); + if (!caching) { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + context._applyShadow(this); + drawFunc.call(this, context); + context.restore(); + } else { + if (!caching) { + context._applyOpacity(this); + context._applyGlobalCompositeOperation(this); + } + drawFunc.call(this, context); + } + } + context.restore(); + return this; + }, + drawHit: function(can, top, caching) { + var layer = this.getLayer(), + canvas = can || layer.hitCanvas, + context = canvas.getContext(), + drawFunc = this.hitFunc() || this.sceneFunc(), + cachedCanvas = this._cache.canvas, + cachedHitCanvas = cachedCanvas && cachedCanvas.hit; + + if (!this.shouldDrawHit(canvas)) { + return this; + } + if (layer) { + layer.clearHitCache(); + } + if (cachedHitCanvas) { + context.save(); + layer._applyTransform(this, context, top); + this._drawCachedHitCanvas(context); + context.restore(); + return this; + } + if (!drawFunc) { + return this; + } + context.save(); + context._applyLineJoin(this); + if (!caching) { + if (layer) { + layer._applyTransform(this, context, top); + } else { + var o = this.getAbsoluteTransform(top).getMatrix(); + context.transform(o[0], o[1], o[2], o[3], o[4], o[5]); + } + } + drawFunc.call(this, context); + context.restore(); + return this; + }, + /** * draw hit graph using the cached scene canvas * @method * @memberof Konva.Shape.prototype @@ -387,53 +446,58 @@ * shape.cache(); * shape.drawHitFromCache(); */ - drawHitFromCache: function(alphaThreshold) { - var threshold = alphaThreshold || 0, - cachedCanvas = this._cache.canvas, - sceneCanvas = this._getCachedSceneCanvas(), - hitCanvas = cachedCanvas.hit, - hitContext = hitCanvas.getContext(), - hitWidth = hitCanvas.getWidth(), - hitHeight = hitCanvas.getHeight(), - hitImageData, hitData, len, rgbColorKey, i, alpha; + drawHitFromCache: function(alphaThreshold) { + var threshold = alphaThreshold || 0, + cachedCanvas = this._cache.canvas, + sceneCanvas = this._getCachedSceneCanvas(), + hitCanvas = cachedCanvas.hit, + hitContext = hitCanvas.getContext(), + hitWidth = hitCanvas.getWidth(), + hitHeight = hitCanvas.getHeight(), + hitImageData, + hitData, + len, + rgbColorKey, + i, + alpha; - hitContext.clear(); - hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); + hitContext.clear(); + hitContext.drawImage(sceneCanvas._canvas, 0, 0, hitWidth, hitHeight); - try { - hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); - hitData = hitImageData.data; - len = hitData.length; - rgbColorKey = Konva.Util._hexToRgb(this.colorKey); + try { + hitImageData = hitContext.getImageData(0, 0, hitWidth, hitHeight); + hitData = hitImageData.data; + len = hitData.length; + rgbColorKey = Konva.Util._hexToRgb(this.colorKey); - // replace non transparent pixels with color key - for(i = 0; i < len; i += 4) { - alpha = hitData[i + 3]; - if (alpha > threshold) { - hitData[i] = rgbColorKey.r; - hitData[i + 1] = rgbColorKey.g; - hitData[i + 2] = rgbColorKey.b; - hitData[i + 3] = 255; - } - else { - hitData[i + 3] = 0; - } - } - hitContext.putImageData(hitImageData, 0, 0); - } - catch(e) { - Konva.Util.error('Unable to draw hit graph from cached scene canvas. ' + e.message); - } - - return this; + // replace non transparent pixels with color key + for (i = 0; i < len; i += 4) { + alpha = hitData[i + 3]; + if (alpha > threshold) { + hitData[i] = rgbColorKey.r; + hitData[i + 1] = rgbColorKey.g; + hitData[i + 2] = rgbColorKey.b; + hitData[i + 3] = 255; + } else { + hitData[i + 3] = 0; + } } - }); - Konva.Util.extend(Konva.Shape, Konva.Node); + hitContext.putImageData(hitImageData, 0, 0); + } catch (e) { + Konva.Util.error( + 'Unable to draw hit graph from cached scene canvas. ' + e.message + ); + } - // add getters and setters - Konva.Factory.addGetterSetter(Konva.Shape, 'stroke'); + return this; + } + }); + Konva.Util.extend(Konva.Shape, Konva.Node); - /** + // add getters and setters + Konva.Factory.addGetterSetter(Konva.Shape, 'stroke'); + + /** * get/set stroke color * @name stroke * @method @@ -457,15 +521,34 @@ * shape.stroke('rgba(0,255,0,0.5'); */ - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeRed', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeGreen', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeBlue', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'strokeAlpha', 1, Konva.Validators.alphaComponent); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'strokeAlpha', + 1, + Konva.Validators.alphaComponent + ); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeWidth', 2); - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeWidth', 2); - - /** + /** * get/set stroke width * @name strokeWidth * @method @@ -480,9 +563,9 @@ * shape.strokeWidth(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeHitEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeHitEnabled', true); - /** + /** * get/set strokeHitEnabled property. Useful for performance optimization. * You may set `shape.strokeHitEnabled(false)`. In this case stroke will be no draw on hit canvas, so hit area * of shape will be decreased (by lineWidth / 2). Remember that non closed line with `strokeHitEnabled = false` @@ -501,9 +584,9 @@ * shape.strokeHitEnabled(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'perfectDrawEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'perfectDrawEnabled', true); - /** + /** * get/set perfectDrawEnabled. If a shape has fill, stroke and opacity you may set `perfectDrawEnabled` to false to improve performance. * See http://konvajs.github.io/docs/performance/Disable_Perfect_Draw.html for more information. * Default value is true @@ -520,9 +603,9 @@ * shape.perfectDrawEnabled(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowForStrokeEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowForStrokeEnabled', true); - /** + /** * get/set shadowForStrokeEnabled. Useful for performance optimization. * You may set `shape.shadowForStrokeEnabled(false)`. In this case stroke will be no draw shadow for stroke. * Remember if you set `shadowForStrokeEnabled = false` for non closed line - that line with have no shadow!. @@ -540,9 +623,9 @@ * shape.shadowForStrokeEnabled(); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'lineJoin'); + Konva.Factory.addGetterSetter(Konva.Shape, 'lineJoin'); - /** + /** * get/set line join. Can be miter, round, or bevel. The * default is miter * @name lineJoin @@ -558,9 +641,9 @@ * shape.lineJoin('round'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'lineCap'); + Konva.Factory.addGetterSetter(Konva.Shape, 'lineCap'); - /** + /** * get/set line cap. Can be butt, round, or square * @name lineCap * @method @@ -575,9 +658,9 @@ * shape.lineCap('round'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'sceneFunc'); + Konva.Factory.addGetterSetter(Konva.Shape, 'sceneFunc'); - /** + /** * get/set scene draw function * @name sceneFunc * @method @@ -597,9 +680,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'hitFunc'); + Konva.Factory.addGetterSetter(Konva.Shape, 'hitFunc'); - /** + /** * get/set hit draw function * @name hitFunc * @method @@ -619,9 +702,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'dash'); + Konva.Factory.addGetterSetter(Konva.Shape, 'dash'); - /** + /** * get/set dash array for stroke. * @name dash * @method @@ -637,10 +720,24 @@ * line.dash([10, 20, 0.001, 20]); */ + Konva.Factory.addGetterSetter(Konva.Shape, 'dashOffset', 0); - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowColor'); + /** + * get/set dash offset for stroke. + * @name dash + * @method + * @memberof Konva.Shape.prototype + * @param {Number} dash offset + * @returns {Number} + * @example + * // apply dashed stroke that is 10px long and 5 pixels apart with an offset of 5px + * line.dash([10, 5]); + * line.dashOffset(5); + */ - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowColor'); + + /** * get/set shadow color * @name shadowColor * @method @@ -664,14 +761,34 @@ * shape.shadowColor('rgba(0,255,0,0.5'); */ - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowRed', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowGreen', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowBlue', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'shadowAlpha', 1, Konva.Validators.alphaComponent); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'shadowAlpha', + 1, + Konva.Validators.alphaComponent + ); - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowBlur'); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowBlur'); - /** + /** * get/set shadow blur * @name shadowBlur * @method @@ -686,9 +803,9 @@ * shape.shadowBlur(10); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOpacity'); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOpacity'); - /** + /** * get/set shadow opacity. must be a value between 0 and 1 * @name shadowOpacity * @method @@ -703,9 +820,12 @@ * shape.shadowOpacity(0.5); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'shadowOffset', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'shadowOffset', [ + 'x', + 'y' + ]); - /** + /** * get/set shadow offset * @name shadowOffset * @method @@ -725,9 +845,9 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetX', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetX', 0); - /** + /** * get/set shadow offset x * @name shadowOffsetX * @method @@ -742,9 +862,9 @@ * shape.shadowOffsetX(5); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetY', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowOffsetY', 0); - /** + /** * get/set shadow offset y * @name shadowOffsetY * @method @@ -759,9 +879,9 @@ * shape.shadowOffsetY(5); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternImage'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternImage'); - /** + /** * get/set fill pattern image * @name fillPatternImage * @method @@ -780,9 +900,9 @@ * imageObj.src = 'path/to/image/jpg'; */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fill'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fill'); - /** + /** * get/set fill color * @name fill * @method @@ -809,14 +929,34 @@ * shape.fill(null); */ - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillRed', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillGreen', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillBlue', 0, Konva.Validators.RGBComponent); - Konva.Factory.addDeprecatedGetterSetter(Konva.Shape, 'fillAlpha', 1, Konva.Validators.alphaComponent); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillRed', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillGreen', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillBlue', + 0, + Konva.Validators.RGBComponent + ); + Konva.Factory.addDeprecatedGetterSetter( + Konva.Shape, + 'fillAlpha', + 1, + Konva.Validators.alphaComponent + ); - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternX', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternX', 0); - /** + /** * get/set fill pattern x * @name fillPatternX * @method @@ -830,9 +970,9 @@ * shape.fillPatternX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternY', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternY', 0); - /** + /** * get/set fill pattern y * @name fillPatternY * @method @@ -846,9 +986,9 @@ * shape.fillPatternY(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientColorStops'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientColorStops'); - /** + /** * get/set fill linear gradient color stops * @name fillLinearGradientColorStops * @method @@ -864,9 +1004,13 @@ * shape.fillLinearGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientStartRadius', 0); + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartRadius', + 0 + ); - /** + /** * get/set fill radial gradient start radius * @name fillRadialGradientStartRadius * @method @@ -881,9 +1025,9 @@ * shape.fillRadialGradientStartRadius(0); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndRadius', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndRadius', 0); - /** + /** * get/set fill radial gradient end radius * @name fillRadialGradientEndRadius * @method @@ -898,9 +1042,9 @@ * shape.fillRadialGradientEndRadius(100); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientColorStops'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientColorStops'); - /** + /** * get/set fill radial gradient color stops * @name fillRadialGradientColorStops * @method @@ -916,9 +1060,9 @@ * shape.fillRadialGradientColorStops(0, 'red', 0.5, 'blue', 1, 'green'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRepeat', 'repeat'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRepeat', 'repeat'); - /** + /** * get/set fill pattern repeat. Can be 'repeat', 'repeat-x', 'repeat-y', or 'no-repeat'. The default is 'repeat' * @name fillPatternRepeat * @method @@ -936,9 +1080,9 @@ * shape.fillPatternRepeat('no repeat'); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillEnabled', true); - /** + /** * get/set fill enabled flag * @name fillEnabled * @method @@ -956,9 +1100,9 @@ * shape.fillEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeEnabled', true); - /** + /** * get/set stroke enabled flag * @name strokeEnabled * @method @@ -976,9 +1120,9 @@ * shape.strokeEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'shadowEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'shadowEnabled', true); - /** + /** * get/set shadow enabled flag * @name shadowEnabled * @method @@ -996,9 +1140,9 @@ * shape.shadowEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'dashEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'dashEnabled', true); - /** + /** * get/set dash enabled flag * @name dashEnabled * @method @@ -1016,9 +1160,9 @@ * shape.dashEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'strokeScaleEnabled', true); + Konva.Factory.addGetterSetter(Konva.Shape, 'strokeScaleEnabled', true); - /** + /** * get/set strokeScale enabled flag * @name strokeScaleEnabled * @method @@ -1036,9 +1180,9 @@ * shape.strokeScaleEnabled(true); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPriority', 'color'); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPriority', 'color'); - /** + /** * get/set fill priority. can be color, pattern, linear-gradient, or radial-gradient. The default is color. * This is handy if you want to toggle between different fill types. * @name fillPriority @@ -1054,9 +1198,12 @@ * shape.fillPriority('linear-gradient'); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternOffset', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternOffset', [ + 'x', + 'y' + ]); - /** + /** * get/set fill pattern offset * @name fillPatternOffset * @method @@ -1076,9 +1223,8 @@ * }); */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetX', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetX', 0); + /** * get/set fill pattern offset x * @name fillPatternOffsetX * @method @@ -1093,8 +1239,8 @@ * shape.fillPatternOffsetX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetY', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternOffsetY', 0); + /** * get/set fill pattern offset y * @name fillPatternOffsetY * @method @@ -1109,9 +1255,12 @@ * shape.fillPatternOffsetY(10); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternScale', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillPatternScale', [ + 'x', + 'y' + ]); - /** + /** * get/set fill pattern scale * @name fillPatternScale * @method @@ -1131,9 +1280,8 @@ * }); */ - - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleX', 1); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleX', 1); + /** * get/set fill pattern scale x * @name fillPatternScaleX * @method @@ -1148,8 +1296,8 @@ * shape.fillPatternScaleX(2); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleY', 1); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternScaleY', 1); + /** * get/set fill pattern scale y * @name fillPatternScaleY * @method @@ -1164,9 +1312,13 @@ * shape.fillPatternScaleY(2); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillLinearGradientStartPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPoint', + ['x', 'y'] + ); - /** + /** * get/set fill linear gradient start point * @name fillLinearGradientStartPoint * @method @@ -1186,8 +1338,12 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientStartPointX', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPointX', + 0 + ); + /** * get/set fill linear gradient start point x * @name fillLinearGradientStartPointX * @method @@ -1202,8 +1358,12 @@ * shape.fillLinearGradientStartPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientStartPointY', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillLinearGradientStartPointY', + 0 + ); + /** * get/set fill linear gradient start point y * @name fillLinearGradientStartPointY * @method @@ -1218,9 +1378,13 @@ * shape.fillLinearGradientStartPointY(20); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillLinearGradientEndPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillLinearGradientEndPoint', + ['x', 'y'] + ); - /** + /** * get/set fill linear gradient end point * @name fillLinearGradientEndPoint * @method @@ -1240,8 +1404,8 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointX', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointX', 0); + /** * get/set fill linear gradient end point x * @name fillLinearGradientEndPointX * @method @@ -1256,8 +1420,8 @@ * shape.fillLinearGradientEndPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointY', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillLinearGradientEndPointY', 0); + /** * get/set fill linear gradient end point y * @name fillLinearGradientEndPointY * @method @@ -1272,9 +1436,13 @@ * shape.fillLinearGradientEndPointY(20); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillRadialGradientStartPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPoint', + ['x', 'y'] + ); - /** + /** * get/set fill radial gradient start point * @name fillRadialGradientStartPoint * @method @@ -1294,8 +1462,12 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientStartPointX', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPointX', + 0 + ); + /** * get/set fill radial gradient start point x * @name fillRadialGradientStartPointX * @method @@ -1310,8 +1482,12 @@ * shape.fillRadialGradientStartPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientStartPointY', 0); - /** + Konva.Factory.addGetterSetter( + Konva.Shape, + 'fillRadialGradientStartPointY', + 0 + ); + /** * get/set fill radial gradient start point y * @name fillRadialGradientStartPointY * @method @@ -1326,9 +1502,13 @@ * shape.fillRadialGradientStartPointY(20); */ - Konva.Factory.addComponentsGetterSetter(Konva.Shape, 'fillRadialGradientEndPoint', ['x', 'y']); + Konva.Factory.addComponentsGetterSetter( + Konva.Shape, + 'fillRadialGradientEndPoint', + ['x', 'y'] + ); - /** + /** * get/set fill radial gradient end point * @name fillRadialGradientEndPoint * @method @@ -1348,8 +1528,8 @@ * }); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointX', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointX', 0); + /** * get/set fill radial gradient end point x * @name fillRadialGradientEndPointX * @method @@ -1364,8 +1544,8 @@ * shape.fillRadialGradientEndPointX(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointY', 0); - /** + Konva.Factory.addGetterSetter(Konva.Shape, 'fillRadialGradientEndPointY', 0); + /** * get/set fill radial gradient end point y * @name fillRadialGradientEndPointY * @method @@ -1380,9 +1560,9 @@ * shape.fillRadialGradientEndPointY(20); */ - Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRotation', 0); + Konva.Factory.addGetterSetter(Konva.Shape, 'fillPatternRotation', 0); - /** + /** * get/set fill pattern rotation in degrees * @name fillPatternRotation * @method @@ -1397,20 +1577,19 @@ * shape.fillPatternRotation(20); */ + Konva.Factory.backCompat(Konva.Shape, { + dashArray: 'dash', + getDashArray: 'getDash', + setDashArray: 'getDash', - Konva.Factory.backCompat(Konva.Shape, { - dashArray: 'dash', - getDashArray: 'getDash', - setDashArray: 'getDash', + drawFunc: 'sceneFunc', + getDrawFunc: 'getSceneFunc', + setDrawFunc: 'setSceneFunc', - drawFunc: 'sceneFunc', - getDrawFunc: 'getSceneFunc', - setDrawFunc: 'setSceneFunc', + drawHitFunc: 'hitFunc', + getDrawHitFunc: 'getHitFunc', + setDrawHitFunc: 'setHitFunc' + }); - drawHitFunc: 'hitFunc', - getDrawHitFunc: 'getHitFunc', - setDrawHitFunc: 'setHitFunc' - }); - - Konva.Collection.mapMethods(Konva.Shape); + Konva.Collection.mapMethods(Konva.Shape); })(Konva); diff --git a/src/Stage.js b/src/Stage.js index fee863b6..cb28c29b 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -1,63 +1,76 @@ (function() { - 'use strict'; - // CONSTANTS - var STAGE = 'Stage', - STRING = 'string', - PX = 'px', + 'use strict'; + // CONSTANTS + var STAGE = 'Stage', + STRING = 'string', + PX = 'px', + MOUSEOUT = 'mouseout', + MOUSELEAVE = 'mouseleave', + MOUSEOVER = 'mouseover', + MOUSEENTER = 'mouseenter', + MOUSEMOVE = 'mousemove', + MOUSEDOWN = 'mousedown', + MOUSEUP = 'mouseup', + CONTEXTMENU = 'contextmenu', + CLICK = 'click', + DBL_CLICK = 'dblclick', + TOUCHSTART = 'touchstart', + TOUCHEND = 'touchend', + TAP = 'tap', + DBL_TAP = 'dbltap', + TOUCHMOVE = 'touchmove', + DOMMOUSESCROLL = 'DOMMouseScroll', + MOUSEWHEEL = 'mousewheel', + WHEEL = 'wheel', + CONTENT_MOUSEOUT = 'contentMouseout', + CONTENT_MOUSEOVER = 'contentMouseover', + CONTENT_MOUSEMOVE = 'contentMousemove', + CONTENT_MOUSEDOWN = 'contentMousedown', + CONTENT_MOUSEUP = 'contentMouseup', + CONTENT_CONTEXTMENU = 'contentContextmenu', + CONTENT_CLICK = 'contentClick', + CONTENT_DBL_CLICK = 'contentDblclick', + CONTENT_TOUCHSTART = 'contentTouchstart', + CONTENT_TOUCHEND = 'contentTouchend', + CONTENT_DBL_TAP = 'contentDbltap', + CONTENT_TAP = 'contentTap', + CONTENT_TOUCHMOVE = 'contentTouchmove', + CONTENT_WHEEL = 'contentWheel', + DIV = 'div', + RELATIVE = 'relative', + KONVA_CONTENT = 'konvajs-content', + SPACE = ' ', + UNDERSCORE = '_', + CONTAINER = 'container', + EMPTY_STRING = '', + EVENTS = [ + MOUSEDOWN, + MOUSEMOVE, + MOUSEUP, + MOUSEOUT, + TOUCHSTART, + TOUCHMOVE, + TOUCHEND, + MOUSEOVER, + DOMMOUSESCROLL, + MOUSEWHEEL, + WHEEL, + CONTEXTMENU + ], + // cached variables + eventsLength = EVENTS.length; - MOUSEOUT = 'mouseout', - MOUSELEAVE = 'mouseleave', - MOUSEOVER = 'mouseover', - MOUSEENTER = 'mouseenter', - MOUSEMOVE = 'mousemove', - MOUSEDOWN = 'mousedown', - MOUSEUP = 'mouseup', - CONTEXTMENU = 'contextmenu', - CLICK = 'click', - DBL_CLICK = 'dblclick', - TOUCHSTART = 'touchstart', - TOUCHEND = 'touchend', - TAP = 'tap', - DBL_TAP = 'dbltap', - TOUCHMOVE = 'touchmove', - DOMMOUSESCROLL = 'DOMMouseScroll', - MOUSEWHEEL = 'mousewheel', - WHEEL = 'wheel', + function addEvent(ctx, eventName) { + ctx.content.addEventListener( + eventName, + function(evt) { + ctx[UNDERSCORE + eventName](evt); + }, + false + ); + } - CONTENT_MOUSEOUT = 'contentMouseout', - CONTENT_MOUSEOVER = 'contentMouseover', - CONTENT_MOUSEMOVE = 'contentMousemove', - CONTENT_MOUSEDOWN = 'contentMousedown', - CONTENT_MOUSEUP = 'contentMouseup', - CONTENT_CONTEXTMENU = 'contentContextmenu', - CONTENT_CLICK = 'contentClick', - CONTENT_DBL_CLICK = 'contentDblclick', - CONTENT_TOUCHSTART = 'contentTouchstart', - CONTENT_TOUCHEND = 'contentTouchend', - CONTENT_DBL_TAP = 'contentDbltap', - CONTENT_TAP = 'contentTap', - CONTENT_TOUCHMOVE = 'contentTouchmove', - CONTENT_WHEEL = 'contentWheel', - - DIV = 'div', - RELATIVE = 'relative', - KONVA_CONTENT = 'konvajs-content', - SPACE = ' ', - UNDERSCORE = '_', - CONTAINER = 'container', - EMPTY_STRING = '', - EVENTS = [MOUSEDOWN, MOUSEMOVE, MOUSEUP, MOUSEOUT, TOUCHSTART, TOUCHMOVE, TOUCHEND, MOUSEOVER, DOMMOUSESCROLL, MOUSEWHEEL, WHEEL, CONTEXTMENU], - - // cached variables - eventsLength = EVENTS.length; - - function addEvent(ctx, eventName) { - ctx.content.addEventListener(eventName, function(evt) { - ctx[UNDERSCORE + eventName](evt); - }, false); - } - - /** + /** * Stage constructor. A stage is used to contain multiple layers * @constructor * @memberof Konva @@ -72,158 +85,156 @@ * container: 'containerId' // or "#containerId" or ".containerClass" * }); */ - Konva.Stage = function(config) { - this.___init(config); - }; + Konva.Stage = function(config) { + this.___init(config); + }; - Konva.Util.addMethods(Konva.Stage, { - ___init: function(config) { - this.nodeType = STAGE; - // call super constructor - Konva.Container.call(this, config); - this._id = Konva.idCounter++; - this._buildDOM(); - this._bindContentEvents(); - this._enableNestedTransforms = false; - Konva.stages.push(this); - }, - _validateAdd: function(child) { - if (child.getType() !== 'Layer') { - Konva.Util.throw('You may only add layers to the stage.'); - } - }, - /** + Konva.Util.addMethods(Konva.Stage, { + ___init: function(config) { + this.nodeType = STAGE; + // call super constructor + Konva.Container.call(this, config); + this._id = Konva.idCounter++; + this._buildDOM(); + this._bindContentEvents(); + this._enableNestedTransforms = false; + Konva.stages.push(this); + }, + _validateAdd: function(child) { + if (child.getType() !== 'Layer') { + Konva.Util.throw('You may only add layers to the stage.'); + } + }, + /** * set container dom element which contains the stage wrapper div element * @method * @memberof Konva.Stage.prototype * @param {DomElement} container can pass in a dom element or id string */ - setContainer: function (container) { - if (typeof container === STRING) { - if (container.charAt(0) === '.') { - var className = container.slice(1); - container = Konva.document.getElementsByClassName(className)[0]; - } else { - var id; - if (container.charAt(0) !== '#') { - id = container; - } else { - id = container.slice(1); - } - container = Konva.document.getElementById(id); - } - if (!container) { - throw 'Can not find container in document with id ' + id; - } - } - this._setAttr(CONTAINER, container); - return this; - }, - shouldDrawHit: function() { - return true; - }, - draw: function() { - Konva.Node.prototype.draw.call(this); - return this; - }, - /** + setContainer: function(container) { + if (typeof container === STRING) { + if (container.charAt(0) === '.') { + var className = container.slice(1); + container = Konva.document.getElementsByClassName(className)[0]; + } else { + var id; + if (container.charAt(0) !== '#') { + id = container; + } else { + id = container.slice(1); + } + container = Konva.document.getElementById(id); + } + if (!container) { + throw 'Can not find container in document with id ' + id; + } + } + this._setAttr(CONTAINER, container); + return this; + }, + shouldDrawHit: function() { + return true; + }, + draw: function() { + Konva.Node.prototype.draw.call(this); + return this; + }, + /** * draw layer scene graphs * @name draw * @method * @memberof Konva.Stage.prototype */ - /** + /** * draw layer hit graphs * @name drawHit * @method * @memberof Konva.Stage.prototype */ - /** + /** * set height * @method * @memberof Konva.Stage.prototype * @param {Number} height */ - setHeight: function(height) { - Konva.Node.prototype.setHeight.call(this, height); - this._resizeDOM(); - return this; - }, - /** + setHeight: function(height) { + Konva.Node.prototype.setHeight.call(this, height); + this._resizeDOM(); + return this; + }, + /** * set width * @method * @memberof Konva.Stage.prototype * @param {Number} width */ - setWidth: function(width) { - Konva.Node.prototype.setWidth.call(this, width); - this._resizeDOM(); - return this; - }, - /** + setWidth: function(width) { + Konva.Node.prototype.setWidth.call(this, width); + this._resizeDOM(); + return this; + }, + /** * clear all layers * @method * @memberof Konva.Stage.prototype */ - clear: function() { - var layers = this.children, - len = layers.length, - n; + clear: function() { + var layers = this.children, len = layers.length, n; - for(n = 0; n < len; n++) { - layers[n].clear(); - } - return this; - }, - clone: function(obj) { - if (!obj) { - obj = {}; - } - obj.container = Konva.document.createElement(DIV); - return Konva.Container.prototype.clone.call(this, obj); - }, - /** + for (n = 0; n < len; n++) { + layers[n].clear(); + } + return this; + }, + clone: function(obj) { + if (!obj) { + obj = {}; + } + obj.container = Konva.document.createElement(DIV); + return Konva.Container.prototype.clone.call(this, obj); + }, + /** * destroy stage * @method * @memberof Konva.Stage.prototype */ - destroy: function() { - var content = this.content; - Konva.Container.prototype.destroy.call(this); + destroy: function() { + var content = this.content; + Konva.Container.prototype.destroy.call(this); - if(content && Konva.Util._isInDocument(content)) { - this.getContainer().removeChild(content); - } - var index = Konva.stages.indexOf(this); - if (index > -1) { - Konva.stages.splice(index, 1); - } - return this; - }, - /** + if (content && Konva.Util._isInDocument(content)) { + this.getContainer().removeChild(content); + } + var index = Konva.stages.indexOf(this); + if (index > -1) { + Konva.stages.splice(index, 1); + } + return this; + }, + /** * get pointer position which can be a touch position or mouse position * @method * @memberof Konva.Stage.prototype * @returns {Object} */ - getPointerPosition: function() { - return this.pointerPos; - }, - getStage: function() { - return this; - }, - /** + getPointerPosition: function() { + return this.pointerPos; + }, + getStage: function() { + return this; + }, + /** * get stage content div element which has the * the class name "konvajs-content" * @method * @memberof Konva.Stage.prototype */ - getContent: function() { - return this.content; - }, - /** + getContent: function() { + return this.content; + }, + /** * Creates a composite data URL * @method * @memberof Konva.Stage.prototype @@ -239,41 +250,46 @@ * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 * is very high quality */ - toDataURL: function(config) { - config = config || {}; + toDataURL: function(config) { + config = config || {}; - var mimeType = config.mimeType || null, - quality = config.quality || null, - x = config.x || 0, - y = config.y || 0, - canvas = new Konva.SceneCanvas({ - width: config.width || this.getWidth(), - height: config.height || this.getHeight(), - pixelRatio: config.pixelRatio - }), - _context = canvas.getContext()._context, - layers = this.children; + var mimeType = config.mimeType || null, + quality = config.quality || null, + x = config.x || 0, + y = config.y || 0, + canvas = new Konva.SceneCanvas({ + width: config.width || this.getWidth(), + height: config.height || this.getHeight(), + pixelRatio: config.pixelRatio + }), + _context = canvas.getContext()._context, + layers = this.children; - if(x || y) { - _context.translate(-1 * x, -1 * y); - } + if (x || y) { + _context.translate(-1 * x, -1 * y); + } + layers.each(function(layer) { + var width = layer.getCanvas().getWidth(); + var height = layer.getCanvas().getHeight(); + var ratio = layer.getCanvas().getPixelRatio(); + _context.drawImage( + layer.getCanvas()._canvas, + 0, + 0, + width / ratio, + height / ratio + ); + }); + var src = canvas.toDataURL(mimeType, quality); - layers.each(function(layer) { - var width = layer.getCanvas().getWidth(); - var height = layer.getCanvas().getHeight(); - var ratio = layer.getCanvas().getPixelRatio(); - _context.drawImage(layer.getCanvas()._canvas, 0, 0, width / ratio, height / ratio); - }); - var src = canvas.toDataURL(mimeType, quality); + if (config.callback) { + config.callback(src); + } - if (config.callback) { - config.callback(src); - } - - return src; - }, - /** + return src; + }, + /** * converts stage into an image. * @method * @memberof Konva.Stage.prototype @@ -289,17 +305,17 @@ * you can specify the quality from 0 to 1, where 0 is very poor quality and 1 * is very high quality */ - toImage: function(config) { - var cb = config.callback; + toImage: function(config) { + var cb = config.callback; - config.callback = function(dataUrl) { - Konva.Util._getImage(dataUrl, function(img) { - cb(img); - }); - }; - this.toDataURL(config); - }, - /** + config.callback = function(dataUrl) { + Konva.Util._getImage(dataUrl, function(img) { + cb(img); + }); + }; + this.toDataURL(config); + }, + /** * get visible intersection shape. This is the preferred * method for determining if a point intersects a shape or not * @method @@ -314,45 +330,47 @@ * // or if you interested in shape parent: * var group = stage.getIntersection({x: 50, y: 50}, 'Group'); */ - getIntersection: function(pos, selector) { - var layers = this.getChildren(), - len = layers.length, - end = len - 1, - n, shape; + getIntersection: function(pos, selector) { + var layers = this.getChildren(), + len = layers.length, + end = len - 1, + n, + shape; - for(n = end; n >= 0; n--) { - shape = layers[n].getIntersection(pos, selector); - if (shape) { - return shape; - } - } + for (n = end; n >= 0; n--) { + shape = layers[n].getIntersection(pos, selector); + if (shape) { + return shape; + } + } - return null; - }, - _resizeDOM: function() { - if(this.content) { - var width = this.getWidth(), - height = this.getHeight(), - layers = this.getChildren(), - len = layers.length, - n, layer; + return null; + }, + _resizeDOM: function() { + if (this.content) { + var width = this.getWidth(), + height = this.getHeight(), + layers = this.getChildren(), + len = layers.length, + n, + layer; - // set content dimensions - this.content.style.width = width + PX; - this.content.style.height = height + PX; + // set content dimensions + this.content.style.width = width + PX; + this.content.style.height = height + PX; - this.bufferCanvas.setSize(width, height); - this.bufferHitCanvas.setSize(width, height); + this.bufferCanvas.setSize(width, height); + this.bufferHitCanvas.setSize(width, height); - // set layer dimensions - for(n = 0; n < len; n++) { - layer = layers[n]; - layer.setSize(width, height); - layer.batchDraw(); - } - } - }, - /** + // set layer dimensions + for (n = 0; n < len; n++) { + layer = layers[n]; + layer.setSize(width, height); + layer.batchDraw(); + } + } + }, + /** * add layer or layers to stage * @method * @memberof Konva.Stage.prototype @@ -360,391 +378,409 @@ * @example * stage.add(layer1, layer2, layer3); */ - add: function(layer) { - if (arguments.length > 1) { - for (var i = 0; i < arguments.length; i++) { - this.add(arguments[i]); - } - return this; - } - Konva.Container.prototype.add.call(this, layer); - layer._setCanvasSize(this.width(), this.height()); + add: function(layer) { + if (arguments.length > 1) { + for (var i = 0; i < arguments.length; i++) { + this.add(arguments[i]); + } + return this; + } + Konva.Container.prototype.add.call(this, layer); + layer._setCanvasSize(this.width(), this.height()); - // draw layer and append canvas to container - layer.draw(); - this.content.appendChild(layer.canvas._canvas); + // draw layer and append canvas to container + layer.draw(); + this.content.appendChild(layer.canvas._canvas); - // chainable - return this; - }, - getParent: function() { - return null; - }, - getLayer: function() { - return null; - }, - /** + // chainable + return this; + }, + getParent: function() { + return null; + }, + getLayer: function() { + return null; + }, + /** * returns a {@link Konva.Collection} of layers * @method * @memberof Konva.Stage.prototype */ - getLayers: function() { - return this.getChildren(); - }, - _bindContentEvents: function() { - for (var n = 0; n < eventsLength; n++) { - addEvent(this, EVENTS[n]); - } - }, - _mouseover: function(evt) { - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - this._fire(CONTENT_MOUSEOVER, {evt: evt}); - } - }, - _mouseout: function(evt) { - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var targetShape = this.targetShape; + getLayers: function() { + return this.getChildren(); + }, + _bindContentEvents: function() { + for (var n = 0; n < eventsLength; n++) { + addEvent(this, EVENTS[n]); + } + }, + _mouseover: function(evt) { + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + this._fire(CONTENT_MOUSEOVER, { evt: evt }); + } + }, + _mouseout: function(evt) { + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var targetShape = this.targetShape; - if(targetShape && !Konva.isDragging()) { - targetShape._fireAndBubble(MOUSEOUT, {evt: evt}); - targetShape._fireAndBubble(MOUSELEAVE, {evt: evt}); - this.targetShape = null; - } - this.pointerPos = undefined; + if (targetShape && !Konva.isDragging()) { + targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); + targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); + this.targetShape = null; + } + this.pointerPos = undefined; - this._fire(CONTENT_MOUSEOUT, {evt: evt}); - } - }, - _mousemove: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchmove(evt); - } - // workaround fake mousemove event in chrome browser https://code.google.com/p/chromium/issues/detail?id=161464 - if ((typeof evt.movementX !== 'undefined' || typeof evt.movementY !== 'undefined') && evt.movementY === 0 && evt.movementX === 0) { - return null; - } - if (Konva.UA.mobile) { - return null; - } - this._setPointerPosition(evt); - var shape; + this._fire(CONTENT_MOUSEOUT, { evt: evt }); + } + }, + _mousemove: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchmove(evt); + } + // workaround fake mousemove event in chrome browser https://code.google.com/p/chromium/issues/detail?id=161464 + if ( + (typeof evt.movementX !== 'undefined' || + typeof evt.movementY !== 'undefined') && + evt.movementY === 0 && + evt.movementX === 0 + ) { + return null; + } + if (Konva.UA.mobile) { + return null; + } + this._setPointerPosition(evt); + var shape; - if (!Konva.isDragging()) { - shape = this.getIntersection(this.getPointerPosition()); - if(shape && shape.isListening()) { - if(!Konva.isDragging() && (!this.targetShape || this.targetShape._id !== shape._id)) { - if(this.targetShape) { - this.targetShape._fireAndBubble(MOUSEOUT, {evt: evt}, shape); - this.targetShape._fireAndBubble(MOUSELEAVE, {evt: evt}, shape); - } - shape._fireAndBubble(MOUSEOVER, {evt: evt}, this.targetShape); - shape._fireAndBubble(MOUSEENTER, {evt: evt}, this.targetShape); - this.targetShape = shape; - } - else { - shape._fireAndBubble(MOUSEMOVE, {evt: evt}); - } - } - /* + if (!Konva.isDragging()) { + shape = this.getIntersection(this.getPointerPosition()); + if (shape && shape.isListening()) { + if ( + !Konva.isDragging() && + (!this.targetShape || this.targetShape._id !== shape._id) + ) { + if (this.targetShape) { + this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }, shape); + this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }, shape); + } + shape._fireAndBubble(MOUSEOVER, { evt: evt }, this.targetShape); + shape._fireAndBubble(MOUSEENTER, { evt: evt }, this.targetShape); + this.targetShape = shape; + } else { + shape._fireAndBubble(MOUSEMOVE, { evt: evt }); + } + } else { + /* * if no shape was detected, clear target shape and try * to run mouseout from previous target shape */ - else { - if(this.targetShape && !Konva.isDragging()) { - this.targetShape._fireAndBubble(MOUSEOUT, {evt: evt}); - this.targetShape._fireAndBubble(MOUSELEAVE, {evt: evt}); - this.targetShape = null; - } - - } - - // content event - this._fire(CONTENT_MOUSEMOVE, {evt: evt}); - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _mousedown: function(evt) { - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchstart(evt); - } - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - Konva.listenClickTap = true; - - if (shape && shape.isListening()) { - this.clickStartShape = shape; - shape._fireAndBubble(MOUSEDOWN, {evt: evt}); - } - - // content event - this._fire(CONTENT_MOUSEDOWN, {evt: evt}); - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _mouseup: function(evt) { - - // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event - if (Konva.UA.ieMobile) { - return this._touchend(evt); - } - if (!Konva.UA.mobile) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()), - clickStartShape = this.clickStartShape, - fireDblClick = false, - dd = Konva.DD; - - if(Konva.inDblClickWindow) { - fireDblClick = true; - Konva.inDblClickWindow = false; - } - // don't set inDblClickWindow after dragging - else if (!dd || !dd.justDragged) { - Konva.inDblClickWindow = true; - } else if (dd) { - dd.justDragged = false; - } - - setTimeout(function() { - Konva.inDblClickWindow = false; - }, Konva.dblClickWindow); - - if (shape && shape.isListening()) { - shape._fireAndBubble(MOUSEUP, {evt: evt}); - - // detect if click or double click occurred - if(Konva.listenClickTap && clickStartShape && clickStartShape._id === shape._id) { - shape._fireAndBubble(CLICK, {evt: evt}); - - if(fireDblClick) { - shape._fireAndBubble(DBL_CLICK, {evt: evt}); - } - } - } - // content events - this._fire(CONTENT_MOUSEUP, {evt: evt}); - if (Konva.listenClickTap) { - this._fire(CONTENT_CLICK, {evt: evt}); - if(fireDblClick) { - this._fire(CONTENT_DBL_CLICK, {evt: evt}); - } - } - - Konva.listenClickTap = false; - } - - // always call preventDefault for desktop events because some browsers - // try to drag and drop the canvas element - if (evt.preventDefault) { - evt.preventDefault(); - } - }, - _contextmenu: function(evt) { - this._fire(CONTENT_CONTEXTMENU, { evt: evt }); - }, - _touchstart: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - Konva.listenClickTap = true; - - if (shape && shape.isListening()) { - this.tapStartShape = shape; - shape._fireAndBubble(TOUCHSTART, {evt: evt}); - - // only call preventDefault if the shape is listening for events - if (shape.isListening() && shape.preventDefault() && evt.preventDefault) { - evt.preventDefault(); - } - } - // content event - this._fire(CONTENT_TOUCHSTART, {evt: evt}); - }, - _touchend: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()), - fireDblClick = false; - - if(Konva.inDblClickWindow) { - fireDblClick = true; - Konva.inDblClickWindow = false; - } - else { - Konva.inDblClickWindow = true; - } - - setTimeout(function() { - Konva.inDblClickWindow = false; - }, Konva.dblClickWindow); - - if (shape && shape.isListening()) { - shape._fireAndBubble(TOUCHEND, {evt: evt}); - - // detect if tap or double tap occurred - if(Konva.listenClickTap && this.tapStartShape && shape._id === this.tapStartShape._id) { - shape._fireAndBubble(TAP, {evt: evt}); - - if(fireDblClick) { - shape._fireAndBubble(DBL_TAP, {evt: evt}); - } - } - // only call preventDefault if the shape is listening for events - if (shape.isListening() && shape.preventDefault() && evt.preventDefault) { - evt.preventDefault(); - } - } - // content events - this._fire(CONTENT_TOUCHEND, {evt: evt}); - if (Konva.listenClickTap) { - this._fire(CONTENT_TAP, {evt: evt}); - if(fireDblClick) { - this._fire(CONTENT_DBL_TAP, {evt: evt}); - } - } - - Konva.listenClickTap = false; - }, - _touchmove: function(evt) { - this._setPointerPosition(evt); - var dd = Konva.DD, - shape; - if (!Konva.isDragging()) { - shape = this.getIntersection(this.getPointerPosition()); - if (shape && shape.isListening()) { - shape._fireAndBubble(TOUCHMOVE, {evt: evt}); - // only call preventDefault if the shape is listening for events - if (shape.isListening() && shape.preventDefault() && evt.preventDefault) { - evt.preventDefault(); - } - } - this._fire(CONTENT_TOUCHMOVE, {evt: evt}); - } - if(dd) { - if (Konva.isDragging() && Konva.DD.node.preventDefault()) { - evt.preventDefault(); - } - } - }, - _DOMMouseScroll: function(evt) { - this._mousewheel(evt); - }, - _mousewheel: function(evt) { - this._setPointerPosition(evt); - var shape = this.getIntersection(this.getPointerPosition()); - - if (shape && shape.isListening()) { - shape._fireAndBubble(WHEEL, {evt: evt}); - } - this._fire(CONTENT_WHEEL, {evt: evt}); - }, - _wheel: function(evt) { - this._mousewheel(evt); - }, - _setPointerPosition: function(evt) { - var contentPosition = this._getContentPosition(), - x = null, - y = null; - evt = evt ? evt : window.event; - - // touch events - if(evt.touches !== undefined) { - // currently, only handle one finger - if (evt.touches.length > 0) { - - var touch = evt.touches[0]; - // get the information for finger #1 - x = touch.clientX - contentPosition.left; - y = touch.clientY - contentPosition.top; - } - } - // mouse events - else { - x = evt.clientX - contentPosition.left; - y = evt.clientY - contentPosition.top; - } - if (x !== null && y !== null) { - this.pointerPos = { - x: x, - y: y - }; - } - }, - _getContentPosition: function() { - var rect = this.content.getBoundingClientRect ? this.content.getBoundingClientRect() : { top: 0, left: 0 }; - return { - top: rect.top, - left: rect.left - }; - }, - _buildDOM: function() { - var container = this.getContainer(); - if (!container) { - if (Konva.Util.isBrowser()) { - throw 'Stage has no container. A container is required.'; - } else { - // automatically create element for jsdom in nodejs env - container = Konva.document.createElement(DIV); - } - } - // clear content inside container - container.innerHTML = EMPTY_STRING; - - // content - this.content = Konva.document.createElement(DIV); - this.content.style.position = RELATIVE; - this.content.className = KONVA_CONTENT; - this.content.setAttribute('role', 'presentation'); - container.appendChild(this.content); - - // the buffer canvas pixel ratio must be 1 because it is used as an - // intermediate canvas before copying the result onto a scene canvas. - // not setting it to 1 will result in an over compensation - this.bufferCanvas = new Konva.SceneCanvas(); - this.bufferHitCanvas = new Konva.HitCanvas({pixelRatio: 1}); - - this._resizeDOM(); - }, - _onContent: function(typesStr, handler) { - var types = typesStr.split(SPACE), - len = types.length, - n, baseEvent; - - for(n = 0; n < len; n++) { - baseEvent = types[n]; - this.content.addEventListener(baseEvent, handler, false); - } - }, - // currently cache function is now working for stage, because stage has no its own canvas element - // TODO: may be it is better to cache all children layers? - cache: function() { - Konva.Util.warn('Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.'); - }, - clearCache: function() { + if (this.targetShape && !Konva.isDragging()) { + this.targetShape._fireAndBubble(MOUSEOUT, { evt: evt }); + this.targetShape._fireAndBubble(MOUSELEAVE, { evt: evt }); + this.targetShape = null; + } } - }); - Konva.Util.extend(Konva.Stage, Konva.Container); - // add getters and setters - Konva.Factory.addGetter(Konva.Stage, 'container'); - Konva.Factory.addOverloadedGetterSetter(Konva.Stage, 'container'); + // content event + this._fire(CONTENT_MOUSEMOVE, { evt: evt }); + } - /** + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mousedown: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchstart(evt); + } + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Konva.listenClickTap = true; + + if (shape && shape.isListening()) { + this.clickStartShape = shape; + shape._fireAndBubble(MOUSEDOWN, { evt: evt }); + } + + // content event + this._fire(CONTENT_MOUSEDOWN, { evt: evt }); + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _mouseup: function(evt) { + // workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event + if (Konva.UA.ieMobile) { + return this._touchend(evt); + } + if (!Konva.UA.mobile) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()), + clickStartShape = this.clickStartShape, + fireDblClick = false, + dd = Konva.DD; + + if (Konva.inDblClickWindow) { + fireDblClick = true; + Konva.inDblClickWindow = false; + } else if (!dd || !dd.justDragged) { + // don't set inDblClickWindow after dragging + Konva.inDblClickWindow = true; + } else if (dd) { + dd.justDragged = false; + } + + setTimeout(function() { + Konva.inDblClickWindow = false; + }, Konva.dblClickWindow); + + if (shape && shape.isListening()) { + shape._fireAndBubble(MOUSEUP, { evt: evt }); + + // detect if click or double click occurred + if ( + Konva.listenClickTap && + clickStartShape && + clickStartShape._id === shape._id + ) { + shape._fireAndBubble(CLICK, { evt: evt }); + + if (fireDblClick) { + shape._fireAndBubble(DBL_CLICK, { evt: evt }); + } + } + } + // content events + this._fire(CONTENT_MOUSEUP, { evt: evt }); + if (Konva.listenClickTap) { + this._fire(CONTENT_CLICK, { evt: evt }); + if (fireDblClick) { + this._fire(CONTENT_DBL_CLICK, { evt: evt }); + } + } + + Konva.listenClickTap = false; + } + + // always call preventDefault for desktop events because some browsers + // try to drag and drop the canvas element + if (evt.preventDefault) { + evt.preventDefault(); + } + }, + _contextmenu: function(evt) { + this._fire(CONTENT_CONTEXTMENU, { evt: evt }); + }, + _touchstart: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + Konva.listenClickTap = true; + + if (shape && shape.isListening()) { + this.tapStartShape = shape; + shape._fireAndBubble(TOUCHSTART, { evt: evt }); + + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && + shape.preventDefault() && + evt.preventDefault + ) { + evt.preventDefault(); + } + } + // content event + this._fire(CONTENT_TOUCHSTART, { evt: evt }); + }, + _touchend: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()), + fireDblClick = false; + + if (Konva.inDblClickWindow) { + fireDblClick = true; + Konva.inDblClickWindow = false; + } else { + Konva.inDblClickWindow = true; + } + + setTimeout(function() { + Konva.inDblClickWindow = false; + }, Konva.dblClickWindow); + + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHEND, { evt: evt }); + + // detect if tap or double tap occurred + if ( + Konva.listenClickTap && + this.tapStartShape && + shape._id === this.tapStartShape._id + ) { + shape._fireAndBubble(TAP, { evt: evt }); + + if (fireDblClick) { + shape._fireAndBubble(DBL_TAP, { evt: evt }); + } + } + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && + shape.preventDefault() && + evt.preventDefault + ) { + evt.preventDefault(); + } + } + // content events + this._fire(CONTENT_TOUCHEND, { evt: evt }); + if (Konva.listenClickTap) { + this._fire(CONTENT_TAP, { evt: evt }); + if (fireDblClick) { + this._fire(CONTENT_DBL_TAP, { evt: evt }); + } + } + + Konva.listenClickTap = false; + }, + _touchmove: function(evt) { + this._setPointerPosition(evt); + var dd = Konva.DD, shape; + if (!Konva.isDragging()) { + shape = this.getIntersection(this.getPointerPosition()); + if (shape && shape.isListening()) { + shape._fireAndBubble(TOUCHMOVE, { evt: evt }); + // only call preventDefault if the shape is listening for events + if ( + shape.isListening() && + shape.preventDefault() && + evt.preventDefault + ) { + evt.preventDefault(); + } + } + this._fire(CONTENT_TOUCHMOVE, { evt: evt }); + } + if (dd) { + if (Konva.isDragging() && Konva.DD.node.preventDefault()) { + evt.preventDefault(); + } + } + }, + _DOMMouseScroll: function(evt) { + this._mousewheel(evt); + }, + _mousewheel: function(evt) { + this._setPointerPosition(evt); + var shape = this.getIntersection(this.getPointerPosition()); + + if (shape && shape.isListening()) { + shape._fireAndBubble(WHEEL, { evt: evt }); + } + this._fire(CONTENT_WHEEL, { evt: evt }); + }, + _wheel: function(evt) { + this._mousewheel(evt); + }, + _setPointerPosition: function(evt) { + var contentPosition = this._getContentPosition(), x = null, y = null; + evt = evt ? evt : window.event; + + // touch events + if (evt.touches !== undefined) { + // currently, only handle one finger + if (evt.touches.length > 0) { + var touch = evt.touches[0]; + // get the information for finger #1 + x = touch.clientX - contentPosition.left; + y = touch.clientY - contentPosition.top; + } + } else { + // mouse events + x = evt.clientX - contentPosition.left; + y = evt.clientY - contentPosition.top; + } + if (x !== null && y !== null) { + this.pointerPos = { + x: x, + y: y + }; + } + }, + _getContentPosition: function() { + var rect = this.content.getBoundingClientRect + ? this.content.getBoundingClientRect() + : { top: 0, left: 0 }; + return { + top: rect.top, + left: rect.left + }; + }, + _buildDOM: function() { + var container = this.getContainer(); + if (!container) { + if (Konva.Util.isBrowser()) { + throw 'Stage has no container. A container is required.'; + } else { + // automatically create element for jsdom in nodejs env + container = Konva.document.createElement(DIV); + } + } + // clear content inside container + container.innerHTML = EMPTY_STRING; + + // content + this.content = Konva.document.createElement(DIV); + this.content.style.position = RELATIVE; + this.content.className = KONVA_CONTENT; + this.content.setAttribute('role', 'presentation'); + container.appendChild(this.content); + + // the buffer canvas pixel ratio must be 1 because it is used as an + // intermediate canvas before copying the result onto a scene canvas. + // not setting it to 1 will result in an over compensation + this.bufferCanvas = new Konva.SceneCanvas(); + this.bufferHitCanvas = new Konva.HitCanvas({ pixelRatio: 1 }); + + this._resizeDOM(); + }, + _onContent: function(typesStr, handler) { + var types = typesStr.split(SPACE), len = types.length, n, baseEvent; + + for (n = 0; n < len; n++) { + baseEvent = types[n]; + this.content.addEventListener(baseEvent, handler, false); + } + }, + // currently cache function is now working for stage, because stage has no its own canvas element + // TODO: may be it is better to cache all children layers? + cache: function() { + Konva.Util.warn( + 'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.' + ); + }, + clearCache: function() {} + }); + Konva.Util.extend(Konva.Stage, Konva.Container); + + // add getters and setters + Konva.Factory.addGetter(Konva.Stage, 'container'); + Konva.Factory.addOverloadedGetterSetter(Konva.Stage, 'container'); + + /** * get container DOM element * @name container * @method @@ -758,5 +794,4 @@ * body.appendChild(container); * stage.container(container); */ - })(); diff --git a/test/functional/TouchEvents-test.js b/test/functional/TouchEvents-test.js index 55c6c515..730926d1 100644 --- a/test/functional/TouchEvents-test.js +++ b/test/functional/TouchEvents-test.js @@ -1,34 +1,25 @@ suite('TouchEvents', function() { - - // ====================================================== - test('stage content touch events', function() { + // ====================================================== + test('stage content touch events', function() { var stage = addStage(); var layer = new Konva.Layer(); var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle' + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle' }); layer.add(circle); stage.add(layer); - var circleTouchstart = - circleTouchend = - stageContentTouchstart = - stageContentTouchend = - stageContentTouchmove = - stageContentTap = - stageContentDbltap = - 0; + var circleTouchstart = (circleTouchend = stageContentTouchstart = stageContentTouchend = stageContentTouchmove = stageContentTap = stageContentDbltap = 0); var top = stage.content.getBoundingClientRect().top; - circle.on('touchstart', function() { circleTouchstart++; }); @@ -58,14 +49,16 @@ suite('TouchEvents', function() { }); stage._touchstart({ - touches: [{ - clientX: 100, - clientY: 100 + top - }] + touches: [ + { + clientX: 100, + clientY: 100 + top + } + ] }); stage._touchend({ - touches: [] + touches: [] }); assert.equal(circleTouchstart, 1, 1); @@ -75,219 +68,219 @@ suite('TouchEvents', function() { assert.equal(stageContentDbltap, 0, 5); stage._touchstart({ - touches: [{ - clientX: 1, - clientY: 1 + top - }] + touches: [ + { + clientX: 1, + clientY: 1 + top + } + ] }); stage._touchend({ - touches: [] + touches: [] }); assert.equal(stageContentTouchstart, 2, 6); assert.equal(stageContentTouchend, 2, 7); assert.equal(stageContentDbltap, 1, 8); - }); + // ====================================================== + test('touchstart touchend touchmove tap dbltap', function(done) { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'red', + stroke: 'black', + strokeWidth: 4 + }); - // ====================================================== - test('touchstart touchend touchmove tap dbltap', function(done) { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'red', - stroke: 'black', - strokeWidth: 4 - }); + // mobile events + var touchstart = false; + var touchend = false; + var tap = false; + var touchmove = false; + var dbltap = false; - - // mobile events - var touchstart = false; - var touchend = false; - var tap = false; - var touchmove = false; - var dbltap = false; - - - /* + /* * mobile */ - circle.on('touchstart', function() { - touchstart = true; - //log('touchstart'); - //alert('touchstart') - }); - - circle.on('touchend', function() { - touchend = true; - //alert('touchend') - //log('touchend'); - }); - - circle.on('touchmove', function() { - touchmove = true; - //log('touchmove'); - }); - - circle.on('tap', function(evt) { - tap = true; - //log('tap'); - }); - - circle.on('dbltap', function() { - dbltap = true; - //log('dbltap'); - }); - - layer.add(circle); - stage.add(layer); - - var top = stage.content.getBoundingClientRect().top; - - // reset inDoubleClickWindow - Konva.inDblClickWindow = false; - - // touchstart circle - stage._touchstart({ - touches: [{ - clientX: 289, - clientY: 100 + top, - }], - preventDefault: function() { - } - }); - - assert(touchstart, '8) touchstart should be true'); - assert(!touchmove, '8) touchmove should be false'); - assert(!touchend, '8) touchend should be false'); - assert(!tap, '8) tap should be false'); - assert(!dbltap, '8) dbltap should be false'); - - // touchend circle - stage._touchend({ - touches: [], - preventDefault: function() { - } - }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - //Konva.DD._endDrag(); - - assert(touchstart, '9) touchstart should be true'); - assert(!touchmove, '9) touchmove should be false'); - assert(touchend, '9) touchend should be true'); - assert(tap, '9) tap should be true'); - assert(!dbltap, '9) dbltap should be false'); - - // touchstart circle - stage._touchstart({ - touches: [{ - clientX: 289, - clientY: 100 + top, - }], - preventDefault: function() { - } - }); - - assert(touchstart, '10) touchstart should be true'); - assert(!touchmove, '10) touchmove should be false'); - assert(touchend, '10) touchend should be true'); - assert(tap, '10) tap should be true'); - assert(!dbltap, '10) dbltap should be false'); - - // touchend circle to triger dbltap - stage._touchend({ - touches: [], - preventDefault: function() { - } - }); - // end drag is tied to document mouseup and touchend event - // which can't be simulated. call _endDrag manually - //Konva.DD._endDrag(); - - assert(touchstart, '11) touchstart should be true'); - assert(!touchmove, '11) touchmove should be false'); - assert(touchend, '11) touchend should be true'); - assert(tap, '11) tap should be true'); - assert(dbltap, '11) dbltap should be true'); - - setTimeout(function() { - // touchmove circle - stage._touchmove({ - touches: [{ - clientX: 290, - clientY: 100 + top, - }], - preventDefault: function() { - } - }); - - assert(touchstart, '12) touchstart should be true'); - assert(touchmove, '12) touchmove should be true'); - assert(touchend, '12) touchend should be true'); - assert(tap, '12) tap should be true'); - assert(dbltap, '12) dbltap should be true'); - - done(); - }, 17); + circle.on('touchstart', function() { + touchstart = true; + //log('touchstart'); + //alert('touchstart') }); - // test for https://github.com/konvajs/konva/issues/156 - test("touchstart out of shape, then touch end inside shape", function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 100, - y: 100, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle' - }); + circle.on('touchend', function() { + touchend = true; + //alert('touchend') + //log('touchend'); + }); - layer.add(circle); - stage.add(layer); + circle.on('touchmove', function() { + touchmove = true; + //log('touchmove'); + }); - var circleTouchend = - stageContentTouchstart = - stageContentTouchend = - 0; + circle.on('tap', function(evt) { + tap = true; + //log('tap'); + }); - var top = stage.content.getBoundingClientRect().top; + circle.on('dbltap', function() { + dbltap = true; + //log('dbltap'); + }); - circle.on('touchend', function() { - circleTouchend++; - }); + layer.add(circle); + stage.add(layer); - stage.on('contentTouchstart', function() { - stageContentTouchstart++; - }); + var top = stage.content.getBoundingClientRect().top; - stage.on('contentTouchend', function() { - stageContentTouchend++; - }); + // reset inDoubleClickWindow + Konva.inDblClickWindow = false; - stage._touchstart({ - touches: [{ - clientX: 1, - clientY: 1 + top - }] - }); + // touchstart circle + stage._touchstart({ + touches: [ + { + clientX: 289, + clientY: 100 + top + } + ], + preventDefault: function() {} + }); - stage._touchend({ - touches: [{ - clientX: 100, + assert(touchstart, '8) touchstart should be true'); + assert(!touchmove, '8) touchmove should be false'); + assert(!touchend, '8) touchend should be false'); + assert(!tap, '8) tap should be false'); + assert(!dbltap, '8) dbltap should be false'); + + // touchend circle + stage._touchend({ + touches: [], + preventDefault: function() {} + }); + // end drag is tied to document mouseup and touchend event + // which can't be simulated. call _endDrag manually + //Konva.DD._endDrag(); + + assert(touchstart, '9) touchstart should be true'); + assert(!touchmove, '9) touchmove should be false'); + assert(touchend, '9) touchend should be true'); + assert(tap, '9) tap should be true'); + assert(!dbltap, '9) dbltap should be false'); + + // touchstart circle + stage._touchstart({ + touches: [ + { + clientX: 289, + clientY: 100 + top + } + ], + preventDefault: function() {} + }); + + assert(touchstart, '10) touchstart should be true'); + assert(!touchmove, '10) touchmove should be false'); + assert(touchend, '10) touchend should be true'); + assert(tap, '10) tap should be true'); + assert(!dbltap, '10) dbltap should be false'); + + // touchend circle to triger dbltap + stage._touchend({ + touches: [], + preventDefault: function() {} + }); + // end drag is tied to document mouseup and touchend event + // which can't be simulated. call _endDrag manually + //Konva.DD._endDrag(); + + assert(touchstart, '11) touchstart should be true'); + assert(!touchmove, '11) touchmove should be false'); + assert(touchend, '11) touchend should be true'); + assert(tap, '11) tap should be true'); + assert(dbltap, '11) dbltap should be true'); + + setTimeout(function() { + // touchmove circle + stage._touchmove({ + touches: [ + { + clientX: 290, clientY: 100 + top - }] + } + ], + preventDefault: function() {} }); - assert.equal(stageContentTouchstart, 1); - assert.equal(stageContentTouchend, 1); - assert.equal(circleTouchend, 1); + assert(touchstart, '12) touchstart should be true'); + assert(touchmove, '12) touchmove should be true'); + assert(touchend, '12) touchend should be true'); + assert(tap, '12) tap should be true'); + assert(dbltap, '12) dbltap should be true'); + + done(); + }, 17); + }); + + // test for https://github.com/konvajs/konva/issues/156 + test('touchstart out of shape, then touch end inside shape', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 100, + y: 100, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle' }); + + layer.add(circle); + stage.add(layer); + + var circleTouchend = (stageContentTouchstart = stageContentTouchend = 0); + + var top = stage.content.getBoundingClientRect().top; + + circle.on('touchend', function() { + circleTouchend++; + }); + + stage.on('contentTouchstart', function() { + stageContentTouchstart++; + }); + + stage.on('contentTouchend', function() { + stageContentTouchend++; + }); + + stage._touchstart({ + touches: [ + { + clientX: 1, + clientY: 1 + top + } + ] + }); + + stage._touchend({ + touches: [ + { + clientX: 100, + clientY: 100 + top + } + ] + }); + + assert.equal(stageContentTouchstart, 1); + assert.equal(stageContentTouchend, 1); + assert.equal(circleTouchend, 1); + }); }); diff --git a/test/runner.js b/test/runner.js index 7aea198b..c5738fe5 100644 --- a/test/runner.js +++ b/test/runner.js @@ -1,23 +1,25 @@ mocha.ui('tdd'); -mocha.setup("bdd"); +mocha.setup('bdd'); var assert = chai.assert, - konvaContainer = document.getElementById('konva-container'), - origAssertEqual = assert.equal, - origAssert = assert, - origNotEqual = assert.notEqual, - origDeepEqual = assert.deepEqual, - assertionCount = 0, - assertions = document.createElement('em'); + konvaContainer = document.getElementById('konva-container'), + origAssertEqual = assert.equal, + origAssert = assert, + origNotEqual = assert.notEqual, + origDeepEqual = assert.deepEqual, + assertionCount = 0, + assertions = document.createElement('em'); -window.requestAnimFrame = (function(callback){ - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback){ +window.requestAnimFrame = (function(callback) { + return ( + window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { window.setTimeout(callback, 1000 / 30); - }; + } + ); })(); function init() { @@ -35,10 +37,10 @@ function init() { assertions.innerHTML = ++assertionCount; }; - assert.deepEqual = function() { - origDeepEqual.apply(this, arguments); - assertions.innerHTML = ++assertionCount; - }; + assert.deepEqual = function() { + origDeepEqual.apply(this, arguments); + assertions.innerHTML = ++assertionCount; + }; window.onload = function() { var mochaStats = document.getElementById('mocha-stats'); @@ -55,132 +57,133 @@ function init() { li.appendChild(assertions); mochaStats.appendChild(li); } - } + }; //addStats(); } - - - Konva.enableTrace = true; Konva.showWarnings = true; //Konva.pixelRatio = 2; window.isPhantomJS = /PhantomJS/.test(window.navigator.userAgent); function addStats() { - stats = new Stats(); - stats.setMode(0); - stats.domElement.style.position = 'fixed'; - stats.domElement.style.left = '0px'; - stats.domElement.style.top = '0px'; - document.getElementsByTagName('body')[0].appendChild( stats.domElement ); + stats = new Stats(); + stats.setMode(0); + stats.domElement.style.position = 'fixed'; + stats.domElement.style.left = '0px'; + stats.domElement.style.top = '0px'; + document.getElementsByTagName('body')[0].appendChild(stats.domElement); + function animate(lastTime) { + stats.begin(); - function animate(lastTime){ - stats.begin(); + requestAnimFrame(function() { + stats.end(); + animate(lastTime); + }); + } - requestAnimFrame(function(){ - stats.end(); - animate(lastTime); - }); - } - - animate(); + animate(); } - - function addStage() { var container = document.createElement('div'), - stage = new Konva.Stage({ - container: container, - width: 578, - height: 200 - }); + stage = new Konva.Stage({ + container: container, + width: 578, + height: 200 + }); konvaContainer.appendChild(container); return stage; } function createCanvas() { - var canvas = document.createElement('canvas'); - var ratio = (Konva.pixelRatio || window.devicePixelRatio); - canvas.width = 578 * ratio; - canvas.height = 200 * ratio; - canvas.getContext('2d').scale(ratio, ratio); - canvas.ratio = ratio; - return canvas; + var canvas = document.createElement('canvas'); + var ratio = Konva.pixelRatio || window.devicePixelRatio; + canvas.width = 578 * ratio; + canvas.height = 200 * ratio; + canvas.getContext('2d').scale(ratio, ratio); + canvas.ratio = ratio; + return canvas; } -function get (element, content) { - element = document.createElement(element); - if (element && content) { - element.innerHTML = content; - } - return element; +function get(element, content) { + element = document.createElement(element); + if (element && content) { + element.innerHTML = content; + } + return element; } function compareCanvases(canvas1, canvas2, tol) { - // don't test in PhantomJS as it use old chrome engine - // it it has opacity + shadow bug - var equal = imagediff.equal(canvas1, canvas2, tol); - if (!equal) { - var - div = get('div'), - b = get('div', '
Expected:
'), - c = get('div', '
Diff:
'), - diff = imagediff.diff(canvas1, canvas2), - diffCanvas = get('canvas'), - context; + // don't test in PhantomJS as it use old chrome engine + // it it has opacity + shadow bug + var equal = imagediff.equal(canvas1, canvas2, tol); + if (!equal) { + var div = get('div'), + b = get('div', '
Expected:
'), + c = get('div', '
Diff:
'), + diff = imagediff.diff(canvas1, canvas2), + diffCanvas = get('canvas'), + context; - diffCanvas.height = diff.height; - diffCanvas.width = diff.width; + diffCanvas.height = diff.height; + diffCanvas.width = diff.width; - div.style.overflow = 'hidden'; - b.style.float = 'left'; - c.style.float = 'left'; + div.style.overflow = 'hidden'; + b.style.float = 'left'; + c.style.float = 'left'; - canvas2.style.position = ''; - canvas2.style.display = ''; + canvas2.style.position = ''; + canvas2.style.display = ''; - context = diffCanvas.getContext('2d'); - context.putImageData(diff, 0, 0); + context = diffCanvas.getContext('2d'); + context.putImageData(diff, 0, 0); - b.appendChild(canvas2); - c.appendChild(diffCanvas); + b.appendChild(canvas2); + c.appendChild(diffCanvas); - div.appendChild(b); - div.appendChild(c); - konvaContainer.appendChild(div); - } - assert.equal(equal, true, 'Result from Konva is different with canvas result'); + div.appendChild(b); + div.appendChild(c); + konvaContainer.appendChild(div); + } + assert.equal( + equal, + true, + 'Result from Konva is different with canvas result' + ); } function compareLayerAndCanvas(layer, canvas, tol) { - compareCanvases(layer.getCanvas()._canvas, canvas, tol); + compareCanvases(layer.getCanvas()._canvas, canvas, tol); } function compareLayers(layer1, layer2, tol) { - compareLayerAndCanvas(layer1, layer2.getCanvas()._canvas, tol); + compareLayerAndCanvas(layer1, layer2.getCanvas()._canvas, tol); } function cloneAndCompareLayer(layer, tol) { - var layer2 = layer.clone(); - layer.getStage().add(layer2); - layer2.hide(); - compareLayers(layer, layer2, tol); + var layer2 = layer.clone(); + layer.getStage().add(layer2); + layer2.hide(); + compareLayers(layer, layer2, tol); } function cloneAndCompareLayerWithHit(layer, tol) { - var layer2 = layer.clone(); - layer.getStage().add(layer2); - layer2.hide(); - compareLayers(layer, layer2, tol); - compareCanvases(layer.getHitCanvas()._canvas, layer2.getHitCanvas()._canvas, tol); + var layer2 = layer.clone(); + layer.getStage().add(layer2); + layer2.hide(); + compareLayers(layer, layer2, tol); + compareCanvases( + layer.getHitCanvas()._canvas, + layer2.getHitCanvas()._canvas, + tol + ); } function compareSceneAndHit(layer) { - compareLayerAndCanvas(layer, layer.getHitCanvas()._canvas, 254); + compareLayerAndCanvas(layer, layer.getHitCanvas()._canvas, 254); } function addContainer() { @@ -203,66 +206,62 @@ function showHit(layer) { konvaContainer.appendChild(canvas); } -beforeEach(function(){ - var title = document.createElement('h2'), - test = this.currentTest; +beforeEach(function() { + var title = document.createElement('h2'), test = this.currentTest; - title.innerHTML = test.parent.title + ' - ' + test.title; - title.className = 'konva-title'; - konvaContainer.appendChild(title); + title.innerHTML = test.parent.title + ' - ' + test.title; + title.className = 'konva-title'; + konvaContainer.appendChild(title); - // resets - Konva.inDblClickWindow = false; - Konva.DD && (Konva.DD.isDragging = false); - Konva.DD && (Konva.DD.node = undefined); + // resets + Konva.inDblClickWindow = false; + Konva.DD && (Konva.DD.isDragging = false); + Konva.DD && (Konva.DD.node = undefined); }); Konva.UA.mobile = false; -afterEach(function(){ -// Konva.stages.forEach(function(stage) { -// stage.destroy(); -// }); +afterEach(function() { + // Konva.stages.forEach(function(stage) { + // stage.destroy(); + // }); }); Konva.Stage.prototype.simulateMouseDown = function(pos) { - var clientRect = this.content.getBoundingClientRect(); + var top = this.content.getBoundingClientRect().top; - this._mousedown({ - clientX: pos.x + clientRect.left, - clientY: pos.y + clientRect.top, - button: pos.button - }); + this._mousedown({ + clientX: pos.x, + clientY: pos.y + top, + button: pos.button + }); }; Konva.Stage.prototype.simulateMouseMove = function(pos) { - var clientRect = this.content.getBoundingClientRect(); + var top = this.content.getBoundingClientRect().top; - var evt = { - clientX: pos.x + clientRect.left, - clientY: pos.y + clientRect.top, - button: pos.button - }; + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button + }; - this._mousemove(evt); - Konva.DD._drag(evt); + this._mousemove(evt); + Konva.DD._drag(evt); }; Konva.Stage.prototype.simulateMouseUp = function(pos) { - "use strict"; - var clientRect = this.content.getBoundingClientRect(); + var top = this.content.getBoundingClientRect().top; + var evt = { + clientX: pos.x, + clientY: pos.y + top, + button: pos.button + }; - var evt = { - clientX: pos.x + clientRect.left, - clientY: pos.y + clientRect.top, - button: pos.button - }; - - - Konva.DD._endDragBefore(evt); - this._mouseup(evt); - Konva.DD._endDragAfter(evt); -} + Konva.DD._endDragBefore(evt); + this._mouseup(evt); + Konva.DD._endDragAfter(evt); +}; init(); diff --git a/test/unit/Shape-test.js b/test/unit/Shape-test.js index 18baca52..2fd730b0 100644 --- a/test/unit/Shape-test.js +++ b/test/unit/Shape-test.js @@ -1,371 +1,482 @@ suite('Shape', function() { - // ====================================================== - test('test intersects()', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 4 - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.intersects({ - x: 201, - y: 101 - }), true, '(201,101) should intersect the shape'); - - assert.equal(rect.intersects({ - x: 197, - y: 97 - }), false, '(197, 97) should not intersect the shape'); - - assert.equal(rect.intersects({ - x: 250, - y: 125 - }), true, '(250, 125) should intersect the shape'); - - assert.equal(rect.intersects({ - x: 300, - y: 150 - }), true, '(300, 150) should intersect the shape'); - - assert.equal(rect.intersects({ - x: 303, - y: 153 - }), false, '(303, 153) should not intersect the shape'); - + // ====================================================== + test('test intersects()', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 4 }); - // ====================================================== - test('test hasShadow() method', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Shape({ - sceneFunc: function(context) { - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(100, 0); - context.lineTo(100, 100); - context.closePath(); - context.fillStrokeShape(this); - }, - x: 10, - y: 10, - fill: 'green', - stroke: 'blue', - strokeWidth: 5, - shadowColor: 'black', - shadowOffset: 10, - shadowOpacity: 0 - }); + layer.add(rect); + stage.add(layer); - layer.add(shape); - stage.add(layer); + assert.equal( + rect.intersects({ + x: 201, + y: 101 + }), + true, + '(201,101) should intersect the shape' + ); - assert.equal(shape.hasShadow(), false, 'shape should not have a shadow because opacity is 0'); + assert.equal( + rect.intersects({ + x: 197, + y: 97 + }), + false, + '(197, 97) should not intersect the shape' + ); - shape.setShadowOpacity(0.5); + assert.equal( + rect.intersects({ + x: 250, + y: 125 + }), + true, + '(250, 125) should intersect the shape' + ); - assert.equal(shape.hasShadow(), true, 'shape should have a shadow because opacity is nonzero'); + assert.equal( + rect.intersects({ + x: 300, + y: 150 + }), + true, + '(300, 150) should intersect the shape' + ); - shape.setShadowEnabled(false); + assert.equal( + rect.intersects({ + x: 303, + y: 153 + }), + false, + '(303, 153) should not intersect the shape' + ); + }); - assert.equal(shape.hasShadow(), false, 'shape should not have a shadow because it is not enabled'); + // ====================================================== + test('test hasShadow() method', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Shape({ + sceneFunc: function(context) { + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(100, 0); + context.lineTo(100, 100); + context.closePath(); + context.fillStrokeShape(this); + }, + x: 10, + y: 10, + fill: 'green', + stroke: 'blue', + strokeWidth: 5, + shadowColor: 'black', + shadowOffset: 10, + shadowOpacity: 0 }); - // ====================================================== - test('custom shape with fill, stroke, and strokeWidth', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Shape({ - sceneFunc: function(context) { - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(100, 0); - context.lineTo(100, 100); - context.closePath(); - context.fillStrokeShape(this); - }, - x: 200, - y: 100, - fill: 'green', - stroke: 'blue', - strokeWidth: 5 - }); + layer.add(shape); + stage.add(layer); - layer.add(shape); - stage.add(layer); + assert.equal( + shape.hasShadow(), + false, + 'shape should not have a shadow because opacity is 0' + ); + + shape.setShadowOpacity(0.5); + + assert.equal( + shape.hasShadow(), + true, + 'shape should have a shadow because opacity is nonzero' + ); + + shape.setShadowEnabled(false); + + assert.equal( + shape.hasShadow(), + false, + 'shape should not have a shadow because it is not enabled' + ); + }); + + // ====================================================== + test('custom shape with fill, stroke, and strokeWidth', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Shape({ + sceneFunc: function(context) { + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(100, 0); + context.lineTo(100, 100); + context.closePath(); + context.fillStrokeShape(this); + }, + x: 200, + y: 100, + fill: 'green', + stroke: 'blue', + strokeWidth: 5 }); - // ====================================================== - test('add star with translated, scaled, rotated fill', function(done) { - var imageObj = new Image(); - imageObj.onload = function() { - var stage = addStage(); - var layer = new Konva.Layer(); + layer.add(shape); + stage.add(layer); + }); - var star = new Konva.Star({ - x: 200, - y: 100, - numPoints: 5, - innerRadius: 40, - outerRadius: 70, + // ====================================================== + test('add star with translated, scaled, rotated fill', function(done) { + var imageObj = new Image(); + imageObj.onload = function() { + var stage = addStage(); + var layer = new Konva.Layer(); - fillPatternImage: imageObj, - fillPatternX: -20, - fillPatternY: -30, - fillPatternScale: {x: 0.5, y:0.5}, - fillPatternOffset: {x: 219, y: 150}, - fillPatternRotation: 90, - fillPatternRepeat: 'no-repeat', + var star = new Konva.Star({ + x: 200, + y: 100, + numPoints: 5, + innerRadius: 40, + outerRadius: 70, - stroke: 'blue', - strokeWidth: 5, - draggable: true - }); + fillPatternImage: imageObj, + fillPatternX: -20, + fillPatternY: -30, + fillPatternScale: { x: 0.5, y: 0.5 }, + fillPatternOffset: { x: 219, y: 150 }, + fillPatternRotation: 90, + fillPatternRepeat: 'no-repeat', - layer.add(star); - stage.add(layer); + stroke: 'blue', + strokeWidth: 5, + draggable: true + }); - /* + layer.add(star); + stage.add(layer); + + /* var anim = new Konva.Animation(function() { star.attrs.fill.rotation += 0.02; }, layer); anim.start(); */ - assert.equal(star.getFillPatternX(), -20, 'star fill x should be -20'); - assert.equal(star.getFillPatternY(), -30, 'star fill y should be -30'); - assert.equal(star.getFillPatternScale().x, 0.5, 'star fill scale x should be 0.5'); - assert.equal(star.getFillPatternScale().y, 0.5, 'star fill scale y should be 0.5'); - assert.equal(star.getFillPatternOffset().x, 219, 'star fill offset x should be 219'); - assert.equal(star.getFillPatternOffset().y, 150, 'star fill offset y should be 150'); - assert.equal(star.getFillPatternRotation(), 90, 'star fill rotation should be 90'); + assert.equal(star.getFillPatternX(), -20, 'star fill x should be -20'); + assert.equal(star.getFillPatternY(), -30, 'star fill y should be -30'); + assert.equal( + star.getFillPatternScale().x, + 0.5, + 'star fill scale x should be 0.5' + ); + assert.equal( + star.getFillPatternScale().y, + 0.5, + 'star fill scale y should be 0.5' + ); + assert.equal( + star.getFillPatternOffset().x, + 219, + 'star fill offset x should be 219' + ); + assert.equal( + star.getFillPatternOffset().y, + 150, + 'star fill offset y should be 150' + ); + assert.equal( + star.getFillPatternRotation(), + 90, + 'star fill rotation should be 90' + ); - star.setFillPatternRotation(180); + star.setFillPatternRotation(180); - assert.equal(star.getFillPatternRotation(), 180, 'star fill rotation should be 180'); + assert.equal( + star.getFillPatternRotation(), + 180, + 'star fill rotation should be 180' + ); - star.setFillPatternScale({x:1, y:1}); + star.setFillPatternScale({ x: 1, y: 1 }); - assert.equal(star.getFillPatternScale().x, 1, 'star fill scale x should be 1'); - assert.equal(star.getFillPatternScale().y, 1, 'star fill scale y should be 1'); + assert.equal( + star.getFillPatternScale().x, + 1, + 'star fill scale x should be 1' + ); + assert.equal( + star.getFillPatternScale().y, + 1, + 'star fill scale y should be 1' + ); - star.setFillPatternOffset({x:100, y:120}); + star.setFillPatternOffset({ x: 100, y: 120 }); - assert.equal(star.getFillPatternOffset().x, 100, 'star fill offset x should be 100'); - assert.equal(star.getFillPatternOffset().y, 120, 'star fill offset y should be 120'); + assert.equal( + star.getFillPatternOffset().x, + 100, + 'star fill offset x should be 100' + ); + assert.equal( + star.getFillPatternOffset().y, + 120, + 'star fill offset y should be 120' + ); - done(); - - }; - imageObj.src = 'assets/darth-vader.jpg'; - }); - - // ====================================================== - test('test size setters and getters', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 50, - fill: 'red' - }); - - var ellipse = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 50, - fill: 'yellow' - }); - - layer.add(ellipse); - layer.add(circle); - stage.add(layer); - - // circle tests - assert.equal(circle.getWidth(), 100, 'circle width should be 100'); - assert.equal(circle.getHeight(), 100, 'circle height should be 100'); - assert.equal(circle.getSize().width, 100, 'circle width should be 100'); - assert.equal(circle.getSize().height, 100, 'circle height should be 100'); - assert.equal(circle.getRadius(), 50, 'circle radius should be 50'); - - circle.setWidth(200); - - assert.equal(circle.getWidth(), 200, 'circle width should be 200'); - assert.equal(circle.getHeight(), 200, 'circle height should be 200'); - assert.equal(circle.getSize().width, 200, 'circle width should be 200'); - assert.equal(circle.getSize().height, 200, 'circle height should be 200'); - assert.equal(circle.getRadius(), 100, 'circle radius should be 100'); - - - }); - - // ====================================================== - test('set image fill to color then image then linear gradient then back to image', function(done) { - var imageObj = new Image(); - imageObj.onload = function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: 200, - y: 60, - radius: 50, - fill: 'blue' - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(circle.getFill(), 'blue', 'circle fill should be blue'); - - circle.setFill(null); - circle.setFillPatternImage(imageObj); - circle.setFillPatternRepeat('no-repeat'); - circle.setFillPatternOffset({x:-200, y:-70}); - - assert.notEqual(circle.getFillPatternImage(), undefined, 'circle fill image should be defined'); - assert.equal(circle.getFillPatternRepeat(), 'no-repeat', 'circle fill repeat should be no-repeat'); - assert.equal(circle.getFillPatternOffset().x, -200, 'circle fill offset x should be -200'); - assert.equal(circle.getFillPatternOffset().y, -70, 'circle fill offset y should be -70'); - - circle.setFillPatternImage(null); - circle.setFillLinearGradientStartPoint({x:-35,y:-35}); - circle.setFillLinearGradientEndPoint({x:35,y:35}); - circle.setFillLinearGradientColorStops([0, 'red', 1, 'blue']); - - circle.setFillLinearGradientStartPoint(null); - circle.setFillPatternImage(imageObj); - circle.setFillPatternRepeat('repeat'); - circle.setFillPatternOffset({x:0,y:0}); - - layer.draw(); - - done(); - }; - imageObj.src = 'assets/darth-vader.jpg'; - }); - - // ====================================================== - test('test enablers and disablers', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: {x:10, y:10}, - dash: [10, 10], - scaleX: 3 - }); - layer.add(circle); - stage.add(layer); - - assert.equal(circle.getStrokeScaleEnabled(), true); - assert.equal(circle.getFillEnabled(), true, 'fillEnabled should be true'); - assert.equal(circle.getStrokeEnabled(), true, 'strokeEnabled should be true'); - assert.equal(circle.getShadowEnabled(), true, 'shadowEnabled should be true'); - assert.equal(circle.dashEnabled(), true, 'dashEnabled should be true'); - - circle.strokeScaleEnabled(false); - assert.equal(circle.getStrokeScaleEnabled(), false); - - layer.draw(); -// var trace = layer.getContext().getTrace(); - - circle.fillEnabled(false); - assert.equal(circle.getFillEnabled(), false, 'fillEnabled should be false'); - - - circle.strokeEnabled(false); - assert.equal(circle.getStrokeEnabled(), false, 'strokeEnabled should be false'); - - circle.shadowEnabled(false); - assert.equal(circle.getShadowEnabled(), false, 'shadowEnabled should be false'); - - circle.dashEnabled(false); - assert.equal(circle.dashEnabled(), false, 'dashEnabled should be false'); - - // re-enable - - circle.dashEnabled(true); - assert.equal(circle.getDashEnabled(), true, 'dashEnabled should be true'); - - circle.shadowEnabled(true); - assert.equal(circle.getShadowEnabled(), true, 'shadowEnabled should be true'); - - circle.strokeEnabled(true); - assert.equal(circle.getStrokeEnabled(), true, 'strokeEnabled should be true'); - - circle.fillEnabled(true); - assert.equal(circle.getFillEnabled(), true, 'fillEnabled should be true'); - - }); - - // ====================================================== - test('fill with shadow and opacity', function(){ - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - opacity: 0.5, - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: {x:10, y:10}, - shadowOpacity: 0.5 - }); - - layer.add(rect); - stage.add(layer); - - assert.equal(rect.getX(), 100); - assert.equal(rect.getY(), 50); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // rect - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,0.5)'; - context.shadowBlur = 10 * canvas.ratio; - context.shadowOffsetX = 10 * canvas.ratio; - context.shadowOffsetY = 10 * canvas.ratio; - context.fill(); - - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();restore();'); + done(); + }; + imageObj.src = 'assets/darth-vader.jpg'; }); // ====================================================== - test('stroke with shadow and opacity', function(){ - Konva.pixelRatio = 1; + test('test size setters and getters', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 50, + fill: 'red' + }); + + var ellipse = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 50, + fill: 'yellow' + }); + + layer.add(ellipse); + layer.add(circle); + stage.add(layer); + + // circle tests + assert.equal(circle.getWidth(), 100, 'circle width should be 100'); + assert.equal(circle.getHeight(), 100, 'circle height should be 100'); + assert.equal(circle.getSize().width, 100, 'circle width should be 100'); + assert.equal(circle.getSize().height, 100, 'circle height should be 100'); + assert.equal(circle.getRadius(), 50, 'circle radius should be 50'); + + circle.setWidth(200); + + assert.equal(circle.getWidth(), 200, 'circle width should be 200'); + assert.equal(circle.getHeight(), 200, 'circle height should be 200'); + assert.equal(circle.getSize().width, 200, 'circle width should be 200'); + assert.equal(circle.getSize().height, 200, 'circle height should be 200'); + assert.equal(circle.getRadius(), 100, 'circle radius should be 100'); + }); + + // ====================================================== + test('set image fill to color then image then linear gradient then back to image', function( + done + ) { + var imageObj = new Image(); + imageObj.onload = function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: 200, + y: 60, + radius: 50, + fill: 'blue' + }); + + layer.add(circle); + stage.add(layer); + + assert.equal(circle.getFill(), 'blue', 'circle fill should be blue'); + + circle.setFill(null); + circle.setFillPatternImage(imageObj); + circle.setFillPatternRepeat('no-repeat'); + circle.setFillPatternOffset({ x: -200, y: -70 }); + + assert.notEqual( + circle.getFillPatternImage(), + undefined, + 'circle fill image should be defined' + ); + assert.equal( + circle.getFillPatternRepeat(), + 'no-repeat', + 'circle fill repeat should be no-repeat' + ); + assert.equal( + circle.getFillPatternOffset().x, + -200, + 'circle fill offset x should be -200' + ); + assert.equal( + circle.getFillPatternOffset().y, + -70, + 'circle fill offset y should be -70' + ); + + circle.setFillPatternImage(null); + circle.setFillLinearGradientStartPoint({ x: -35, y: -35 }); + circle.setFillLinearGradientEndPoint({ x: 35, y: 35 }); + circle.setFillLinearGradientColorStops([0, 'red', 1, 'blue']); + + circle.setFillLinearGradientStartPoint(null); + circle.setFillPatternImage(imageObj); + circle.setFillPatternRepeat('repeat'); + circle.setFillPatternOffset({ x: 0, y: 0 }); + + layer.draw(); + + done(); + }; + imageObj.src = 'assets/darth-vader.jpg'; + }); + + // ====================================================== + test('test enablers and disablers', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + dash: [10, 10], + scaleX: 3 + }); + layer.add(circle); + stage.add(layer); + + assert.equal(circle.getStrokeScaleEnabled(), true); + assert.equal(circle.getFillEnabled(), true, 'fillEnabled should be true'); + assert.equal( + circle.getStrokeEnabled(), + true, + 'strokeEnabled should be true' + ); + assert.equal( + circle.getShadowEnabled(), + true, + 'shadowEnabled should be true' + ); + assert.equal(circle.dashEnabled(), true, 'dashEnabled should be true'); + + circle.strokeScaleEnabled(false); + assert.equal(circle.getStrokeScaleEnabled(), false); + + layer.draw(); + // var trace = layer.getContext().getTrace(); + + circle.fillEnabled(false); + assert.equal(circle.getFillEnabled(), false, 'fillEnabled should be false'); + + circle.strokeEnabled(false); + assert.equal( + circle.getStrokeEnabled(), + false, + 'strokeEnabled should be false' + ); + + circle.shadowEnabled(false); + assert.equal( + circle.getShadowEnabled(), + false, + 'shadowEnabled should be false' + ); + + circle.dashEnabled(false); + assert.equal(circle.dashEnabled(), false, 'dashEnabled should be false'); + + // re-enable + + circle.dashEnabled(true); + assert.equal(circle.getDashEnabled(), true, 'dashEnabled should be true'); + + circle.shadowEnabled(true); + assert.equal( + circle.getShadowEnabled(), + true, + 'shadowEnabled should be true' + ); + + circle.strokeEnabled(true); + assert.equal( + circle.getStrokeEnabled(), + true, + 'strokeEnabled should be true' + ); + + circle.fillEnabled(true); + assert.equal(circle.getFillEnabled(), true, 'fillEnabled should be true'); + }); + + // ====================================================== + test('fill with shadow and opacity', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + opacity: 0.5, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 }, + shadowOpacity: 0.5 + }); + + layer.add(rect); + stage.add(layer); + + assert.equal(rect.getX(), 100); + assert.equal(rect.getY(), 50); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // rect + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 10 * canvas.ratio; + context.shadowOffsetX = 10 * canvas.ratio; + context.shadowOffsetY = 10 * canvas.ratio; + context.fill(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();restore();restore();' + ); + }); + + // ====================================================== + test('stroke with shadow and opacity', function() { + Konva.pixelRatio = 1; var stage = addStage(); var layer = new Konva.Layer(); @@ -380,7 +491,7 @@ suite('Shape', function() { opacity: 0.5, shadowColor: 'black', shadowBlur: 10, - shadowOffset: {x:10, y:10}, + shadowOffset: { x: 10, y: 10 }, shadowOpacity: 0.5 }); @@ -390,299 +501,298 @@ suite('Shape', function() { assert.equal(rect.getX(), 100); assert.equal(rect.getY(), 50); - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // rect - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // rect + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); - context.strokeStyle = 'red'; - context.lineWidth = 20; + context.strokeStyle = 'red'; + context.lineWidth = 20; + context.shadowColor = 'rgba(0,0,0,0.5)'; + context.shadowBlur = 10 * canvas.ratio; + context.shadowOffsetX = 10 * canvas.ratio; + context.shadowOffsetY = 10 * canvas.ratio; + context.stroke(); - context.shadowColor = 'rgba(0,0,0,0.5)'; - context.shadowBlur = 10 * canvas.ratio; - context.shadowOffsetX = 10 * canvas.ratio; - context.shadowOffsetY = 10 * canvas.ratio; - context.stroke(); - - compareLayerAndCanvas(layer, canvas, 10); + compareLayerAndCanvas(layer, canvas, 10); var trace = layer.getContext().getTrace(); //console.log(trace); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();lineWidth=20;strokeStyle=red;stroke();restore();restore();'); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();globalAlpha=0.5;shadowColor=rgba(0,0,0,0.5);shadowBlur=10;shadowOffsetX=10;shadowOffsetY=10;beginPath();rect(0,0,100,50);closePath();lineWidth=20;strokeStyle=red;stroke();restore();restore();' + ); }); + // ====================================================== + test('fill and stroke with opacity', function() { + var stage = addStage(); - // ====================================================== - test('fill and stroke with opacity', function(){ - var stage = addStage(); + var layer = new Konva.Layer(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - opacity : 0.5 - }); - - layer.add(rect); - - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // stroke - context.beginPath(); - context.moveTo(100,50); - context.lineTo(200,50); - context.lineTo(200,100); - context.lineTo(100,100); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.stroke(); - - // rect - context.fillStyle = 'green'; - context.fillRect(105, 55, 90, 40); - - compareLayerAndCanvas(layer, canvas, 10); + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + opacity: 0.5 }); - // ====================================================== - test('fill and stroke with shadow', function(){ - var stage = addStage(); + layer.add(rect); - var layer = new Konva.Layer(); + stage.add(layer); - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - shadowColor: 'grey', - shadowBlur: 10, - shadowOffset: { - x: 20, - y: 20 - } - }); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // stroke + context.beginPath(); + context.moveTo(100, 50); + context.lineTo(200, 50); + context.lineTo(200, 100); + context.lineTo(100, 100); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.stroke(); - layer.add(rect); - stage.add(layer); + // rect + context.fillStyle = 'green'; + context.fillRect(105, 55, 90, 40); - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'grey'; - context.shadowBlur = 10 * canvas.ratio; - context.shadowOffsetX = 20 * canvas.ratio; - context.shadowOffsetY = 20 * canvas.ratio; - context.lineWidth = 10; - context.stroke(); - context.fill(); - - - - // clear the shadow - context.shadowColor = 0; - context.shadowOffsetX = 0; - context.shadowOffsetY = 0; - context.shadowBlur = 0; - - // restroke without the shaodw - context.stroke(); - - compareLayerAndCanvas(layer, canvas, 10); - - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();'); - }); - - // ====================================================== - test.skip('fill and stroke with shadow and opacity', function(){ - var stage = addStage(); - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - shadowColor: 'grey', - opacity : 0.5, - shadowBlur : 5, - shadowOffset: { - x: 20, - y: 20 - } - }); - - - layer.add(rect); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.3; - - // draw shadow - context.save(); - context.beginPath(); - context.rect(95, 45, 110, 60); - context.closePath(); - context.shadowColor = 'grey'; - context.shadowBlur = 5 * canvas.ratio; - context.shadowOffsetX = 20 * canvas.ratio; - context.shadowOffsetY = 20 * canvas.ratio; - context.fillStyle = 'black'; - context.fill(); - context.restore(); - - // draw "stroke" - context.save(); - context.beginPath(); - context.moveTo(100,50); - context.lineTo(200,50); - context.lineTo(200,100); - context.lineTo(100,100); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.stroke(); - context.restore(); - - context.save(); - context.beginPath(); - context.fillStyle = 'green'; - context.rect(105, 55, 90, 40); - context.closePath(); - context.fill(); - context.restore(); - - - // don't test in PhantomJS as it use old chrome engine - // it it has opacity + shadow bug - if (!window.mochaPhantomJS) { - compareLayerAndCanvas(layer, canvas, 240); - } - - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal(trace, 'clearRect(0,0,578,200);save();save();shadowColor=rgba(128,128,128,1);shadowBlur=1;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();restore();'); - - }); - - // ====================================================== - test('text with fill and stroke with shadow', function(){ - var stage = addStage(); - - var layer = new Konva.Layer(); - - var text = new Konva.Text({ - x: 50, - y: 50, - text : 'Test TEXT', - fontSize : 50, - fill: 'green', - stroke: 'black', - strokeWidth: 2, - shadowColor: 'grey', - shadowBlur: 2, - shadowOffset: { - x: 20, - y: 20 - } - }); - - layer.add(text); - stage.add(layer); - - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - - context.save(); - context.shadowColor = 'grey'; - context.shadowBlur = 2 * canvas.ratio; - context.shadowOffsetX = 20 * canvas.ratio; - context.shadowOffsetY = 20 * canvas.ratio; - context.font = 'normal 50px Arial'; - context.textBaseline = 'middle'; - - context.fillStyle = 'green'; - context.fillText('Test TEXT', 50, 75); - - context.lineWidth = 2; - context.strokeStyle = 'black'; - context.strokeText('Test TEXT', 50, 75); - - context.stroke(); - context.fill(); - context.restore(); - - // draw text again to remove shadow under stroke - context.font = 'normal 50px Arial'; - context.textBaseline = 'middle'; - context.fillText('Test TEXT', 50, 75); - context.fillStyle = 'green'; - context.fillText('Test TEXT', 50, 75); - - context.lineWidth = 2; - context.strokeStyle = 'black'; - context.strokeText('Test TEXT', 50, 75); - - if (!window.isPhantomJS) { - compareLayerAndCanvas(layer, canvas, 254); - } - }); - - - - // ====================================================== - test('shape intersect with shadow', function(){ - var stage = addStage(); - - var layer = new Konva.Layer(); - - var rect = new Konva.Rect({ - fill: '#ff0000', - x: 50, - y: 50, - width: 200, - height: 200, - draggable: true, - shadowColor: '#000' // if all shadow properties removed, works fine - }); - layer.add(rect); - stage.add(layer); - - //error here - assert.equal(rect.intersects({x:52,y:52}), true); - assert.equal(rect.intersects({x:45,y:45}), false); - }); + compareLayerAndCanvas(layer, canvas, 10); + }); // ====================================================== - test('overloaded getters and setters', function(){ + test('fill and stroke with shadow', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + shadowColor: 'grey', + shadowBlur: 10, + shadowOffset: { + x: 20, + y: 20 + } + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'grey'; + context.shadowBlur = 10 * canvas.ratio; + context.shadowOffsetX = 20 * canvas.ratio; + context.shadowOffsetY = 20 * canvas.ratio; + context.lineWidth = 10; + context.stroke(); + context.fill(); + + // clear the shadow + context.shadowColor = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + + // restroke without the shaodw + context.stroke(); + + compareLayerAndCanvas(layer, canvas, 10); + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();' + ); + }); + + // ====================================================== + test.skip('fill and stroke with shadow and opacity', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + shadowColor: 'grey', + opacity: 0.5, + shadowBlur: 5, + shadowOffset: { + x: 20, + y: 20 + } + }); + + layer.add(rect); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.3; + + // draw shadow + context.save(); + context.beginPath(); + context.rect(95, 45, 110, 60); + context.closePath(); + context.shadowColor = 'grey'; + context.shadowBlur = 5 * canvas.ratio; + context.shadowOffsetX = 20 * canvas.ratio; + context.shadowOffsetY = 20 * canvas.ratio; + context.fillStyle = 'black'; + context.fill(); + context.restore(); + + // draw "stroke" + context.save(); + context.beginPath(); + context.moveTo(100, 50); + context.lineTo(200, 50); + context.lineTo(200, 100); + context.lineTo(100, 100); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.stroke(); + context.restore(); + + context.save(); + context.beginPath(); + context.fillStyle = 'green'; + context.rect(105, 55, 90, 40); + context.closePath(); + context.fill(); + context.restore(); + + // don't test in PhantomJS as it use old chrome engine + // it it has opacity + shadow bug + if (!window.mochaPhantomJS) { + compareLayerAndCanvas(layer, canvas, 240); + } + + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();save();shadowColor=rgba(128,128,128,1);shadowBlur=1;shadowOffsetX=20;shadowOffsetY=20;globalAlpha=0.5;drawImage([object HTMLCanvasElement],0,0,578,200);restore();restore();' + ); + }); + + // ====================================================== + test('text with fill and stroke with shadow', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 50, + y: 50, + text: 'Test TEXT', + fontSize: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 2, + shadowColor: 'grey', + shadowBlur: 2, + shadowOffset: { + x: 20, + y: 20 + } + }); + + layer.add(text); + stage.add(layer); + + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + + context.save(); + context.shadowColor = 'grey'; + context.shadowBlur = 2 * canvas.ratio; + context.shadowOffsetX = 20 * canvas.ratio; + context.shadowOffsetY = 20 * canvas.ratio; + context.font = 'normal 50px Arial'; + context.textBaseline = 'middle'; + + context.fillStyle = 'green'; + context.fillText('Test TEXT', 50, 75); + + context.lineWidth = 2; + context.strokeStyle = 'black'; + context.strokeText('Test TEXT', 50, 75); + + context.stroke(); + context.fill(); + context.restore(); + + // draw text again to remove shadow under stroke + context.font = 'normal 50px Arial'; + context.textBaseline = 'middle'; + context.fillText('Test TEXT', 50, 75); + context.fillStyle = 'green'; + context.fillText('Test TEXT', 50, 75); + + context.lineWidth = 2; + context.strokeStyle = 'black'; + context.strokeText('Test TEXT', 50, 75); + + if (!window.isPhantomJS) { + compareLayerAndCanvas(layer, canvas, 254); + } + }); + + // ====================================================== + test('shape intersect with shadow', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + fill: '#ff0000', + x: 50, + y: 50, + width: 200, + height: 200, + draggable: true, + shadowColor: '#000' // if all shadow properties removed, works fine + }); + layer.add(rect); + stage.add(layer); + + //error here + assert.equal(rect.intersects({ x: 52, y: 52 }), true); + assert.equal(rect.intersects({ x: 45, y: 45 }), false); + }); + + // ====================================================== + test('overloaded getters and setters', function() { var stage = addStage(); var layer = new Konva.Layer(); @@ -704,7 +814,6 @@ suite('Shape', function() { rect.stroke('blue'); assert.equal(rect.stroke(), 'blue'); - rect.lineJoin('bevel'); assert.equal(rect.lineJoin(), 'bevel'); @@ -725,59 +834,50 @@ suite('Shape', function() { // NOTE: skipping the rest because it would take hours to test all possible methods. // This should hopefully be enough to test Factor overloaded methods - - }); // ====================================================== test('create image hit region', function(done) { - var imageObj = new Image(); + var imageObj = new Image(); - var stage = addStage(); - var layer = new Konva.Layer(); + var stage = addStage(); + var layer = new Konva.Layer(); - imageObj.onload = function() { + imageObj.onload = function() { + var lion = new Konva.Image({ + x: 200, + y: 40, + image: imageObj, + draggable: true, + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: 20, + shadowOpacity: 0.2 + }); - var lion = new Konva.Image({ - x: 200, - y: 40, - image: imageObj, - draggable: true, - shadowColor: 'black', - shadowBlur: 10, - shadowOffset: 20, - shadowOpacity: 0.2 - }); + // override color key with black + lion.colorKey = '#000000'; + Konva.shapes['#000000'] = lion; - // override color key with black - lion.colorKey = '#000000'; - Konva.shapes['#000000'] = lion; + layer.add(lion); - layer.add(lion); + stage.add(layer); - stage.add(layer); + lion.cache(); - lion.cache(); + //document.body.appendChild(lion._cache.canvas.hit._canvas); + lion.drawHitFromCache(); - //document.body.appendChild(lion._cache.canvas.hit._canvas); + layer.draw(); + done(); + }; + imageObj.src = 'assets/lion.png'; - lion.drawHitFromCache(); + showHit(layer); - - layer.draw(); - - - done(); - - - }; - imageObj.src = 'assets/lion.png'; - - showHit(layer); - - layer.hitCanvas._canvas.style.border='2px solid black'; + layer.hitCanvas._canvas.style.border = '2px solid black'; }); test('test defaults', function() { @@ -795,6 +895,7 @@ suite('Shape', function() { assert.equal(shape.strokeEnabled(), true); assert.equal(shape.shadowEnabled(), true); assert.equal(shape.dashEnabled(), true); + assert.equal(shape.dashOffset(), 0); assert.equal(shape.strokeScaleEnabled(), true); assert.equal(shape.fillPriority(), 'color'); assert.equal(shape.fillPatternOffsetX(), 0); @@ -812,405 +913,458 @@ suite('Shape', function() { assert.equal(shape.fillPatternRotation(), 0); }); - // ====================================================== - test.skip('hit graph when shape cached before adding to Layer', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 290, - y: 111, - width : 50, - height : 50, - fill : 'black' - }); - rect.cache(); + // ====================================================== + test.skip('hit graph when shape cached before adding to Layer', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 290, + y: 111, + width: 50, + height: 50, + fill: 'black' + }); + rect.cache(); - var click = false; + var click = false; - rect.on('click', function() { - click = true; - }); - - layer.add(rect); - stage.add(layer); - - var top = stage.content.getBoundingClientRect().top; - - showHit(layer); - - stage._mousedown({ - clientX: 300, - clientY: 120 + top - }); - - Konva.DD._endDragBefore(); - stage._mouseup({ - clientX: 300, - clientY: 120 + top - }); - Konva.DD._endDragAfter({dragEndNode:rect}); - - //TODO: can't get this to pass - assert.equal(click, true, 'click event should have been fired when mousing down and then up on rect'); + rect.on('click', function() { + click = true; }); - test('class inherince', function() { - var rect = new Konva.Rect(); - assert.equal(rect instanceof Konva.Rect, true); - assert.equal(rect instanceof Konva.Shape, true); - assert.equal(rect instanceof Konva.Node, true); + layer.add(rect); + stage.add(layer); + + var top = stage.content.getBoundingClientRect().top; + + showHit(layer); + + stage.simulateMouseDown({ + x: 300, + y: 120 }); - test('disable stroke for hit', function(){ - var stage = addStage(); + // Konva.DD._endDragBefore(); + stage.simulateMouseUp({ + x: 300, + y: 120 + }); + // Konva.DD._endDragAfter({ dragEndNode: rect }); - var layer = new Konva.Layer(); + //TODO: can't get this to pass + assert.equal( + click, + true, + 'click event should have been fired when mousing down and then up on rect' + ); + }); - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - stroke: 'red', - strokeWidth: 20 - }); - // default value - assert.equal(rect.strokeHitEnabled(), true); + test('class inherince', function() { + var rect = new Konva.Rect(); + assert.equal(rect instanceof Konva.Rect, true); + assert.equal(rect instanceof Konva.Shape, true); + assert.equal(rect instanceof Konva.Node, true); + }); - rect.strokeHitEnabled(false); - assert.equal(rect.strokeHitEnabled(), false); - layer.add(rect); - stage.add(layer); + test('disable stroke for hit', function() { + var stage = addStage(); + var layer = new Konva.Layer(); - assert.equal(rect.getY(), 50); + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + stroke: 'red', + strokeWidth: 20 + }); + // default value + assert.equal(rect.strokeHitEnabled(), true); - var trace = layer.getHitCanvas().getContext().getTrace(true); - assert.equal(trace, 'clearRect();save();transform();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();'); + rect.strokeHitEnabled(false); + assert.equal(rect.strokeHitEnabled(), false); + layer.add(rect); + stage.add(layer); + + assert.equal(rect.getY(), 50); + + var trace = layer.getHitCanvas().getContext().getTrace(true); + assert.equal( + trace, + 'clearRect();save();transform();beginPath();rect();closePath();save();fillStyle;fill();restore();restore();' + ); + }); + + test('cache shadow color rgba', function() { + var circle = new Konva.Circle({ + fill: 'green', + radius: 50 + }); + // no shadow on start + assert.equal(circle.hasShadow(), false); + assert.equal(circle.getShadowRGBA(), undefined); + + // set shadow + circle.shadowColor('black'); + assert.equal(circle.hasShadow(), true); + assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,1)'); + + // set another shadow property + circle.shadowOpacity(0.2); + assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,0.2)'); + + circle.shadowColor('rgba(10,10,10,0.5)'); + assert.equal(circle.getShadowRGBA(), 'rgba(10,10,10,0.1)'); + + // reset shadow + circle.shadowColor(null); + assert.equal(circle.getShadowRGBA(), undefined); + }); + + test('scale should also effect shadow offset', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 100, + scaleX: 0.5, + scaleY: 0.5, + fill: 'green', + shadowColor: 'black', + shadowBlur: 0, + shadowOffset: { x: 10, y: 10 } }); - test('cache shadow color rgba', function() { - var circle = new Konva.Circle({ - fill : 'green', - radius : 50 - }); - // no shadow on start - assert.equal(circle.hasShadow(), false); - assert.equal(circle.getShadowRGBA(), undefined); + layer.add(rect); + stage.add(layer); - // set shadow - circle.shadowColor('black'); - assert.equal(circle.hasShadow(), true); - assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,1)'); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // rect + context.beginPath(); + context.rect(100, 100, 50, 50); + context.closePath(); - // set another shadow property - circle.shadowOpacity(0.2); - assert.equal(circle.getShadowRGBA(), 'rgba(0,0,0,0.2)'); + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,1)'; + context.shadowBlur = 0; + context.shadowOffsetX = 5 * canvas.ratio; + context.shadowOffsetY = 5 * canvas.ratio; + context.fill(); - circle.shadowColor('rgba(10,10,10,0.5)'); - assert.equal(circle.getShadowRGBA(), 'rgba(10,10,10,0.1)'); + compareLayerAndCanvas(layer, canvas, 10); + var trace = layer.getContext().getTrace(); - // reset shadow - circle.shadowColor(null); - assert.equal(circle.getShadowRGBA(), undefined); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(0.5,0,0,0.5,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();restore();' + ); + }); + + test('scale should also effect shadow offset - negative scale', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 100, + width: 100, + height: 100, + scaleX: -0.5, + scaleY: 0.5, + fill: 'green', + shadowColor: 'black', + shadowBlur: 10, + shadowOffset: { x: 10, y: 10 } }); - test('scale should also effect shadow offset', function() { - var stage = addStage(); + layer.add(rect); + stage.add(layer); - var layer = new Konva.Layer(); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // rect + context.beginPath(); + context.rect(50, 100, 50, 50); + context.closePath(); - var rect = new Konva.Rect({ - x: 100, - y: 100, - width: 100, - height: 100, - scaleX : 0.5, - scaleY : 0.5, - fill: 'green', - shadowColor: 'black', - shadowBlur: 0, - shadowOffset: {x:10, y:10} - }); + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,1)'; + context.shadowBlur = 10; + context.shadowOffsetX = -5 * canvas.ratio; + context.shadowOffsetY = 5 * canvas.ratio; + context.fill(); - layer.add(rect); - stage.add(layer); + compareLayerAndCanvas(layer, canvas, 150); - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // rect - context.beginPath(); - context.rect(100, 100, 50, 50); - context.closePath(); + // var trace = layer.getContext().getTrace(); - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,1)'; - context.shadowBlur = 0; - context.shadowOffsetX = 5 * canvas.ratio; - context.shadowOffsetY = 5 * canvas.ratio; - context.fill(); + // assert.equal( + // trace, + // 'clearRect(0,0,578,200);save();transform(-0.5,0,0,0.5,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=10;shadowOffsetX=-5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();restore();' + // ); + }); + test('scale of parent container should also effect shadow offset', function() { + var stage = addStage(); - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(0.5,0,0,0.5,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,100,100);closePath();fillStyle=green;fill();restore();restore();'); + var layer = new Konva.Layer(); + var group = new Konva.Group({ + x: 100, + y: 100, + scaleX: 0.5, + scaleY: 0.5 + }); + var rect = new Konva.Rect({ + width: 200, + height: 200, + scaleX: 0.5, + scaleY: 0.5, + fill: 'green', + shadowColor: 'black', + shadowBlur: 0, + shadowOffset: { x: 20, y: 20 } }); - test('scale of parent container should also effect shadow offset', function() { - var stage = addStage(); + group.add(rect); + layer.add(group); + stage.add(layer); - var layer = new Konva.Layer(); - var group = new Konva.Group({ - x : 100, - y : 100, - scaleX : 0.5, - scaleY : 0.5 - }); - var rect = new Konva.Rect({ - width: 200, - height: 200, - scaleX : 0.5, - scaleY : 0.5, - fill: 'green', - shadowColor: 'black', - shadowBlur: 0, - shadowOffset: {x:20, y:20} - }); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + // rect + context.beginPath(); + context.rect(100, 100, 50, 50); + context.closePath(); - group.add(rect); - layer.add(group); - stage.add(layer); + context.fillStyle = 'green'; + context.shadowColor = 'rgba(0,0,0,1)'; + context.shadowBlur = 0; + context.shadowOffsetX = 5 * canvas.ratio; + context.shadowOffsetY = 5 * canvas.ratio; + context.fill(); - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - // rect - context.beginPath(); - context.rect(100, 100, 50, 50); - context.closePath(); + compareLayerAndCanvas(layer, canvas, 10); - context.fillStyle = 'green'; - context.shadowColor = 'rgba(0,0,0,1)'; - context.shadowBlur = 0; - context.shadowOffsetX = 5 * canvas.ratio; - context.shadowOffsetY = 5 * canvas.ratio; - context.fill(); + var trace = layer.getContext().getTrace(); - compareLayerAndCanvas(layer, canvas, 10); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(0.25,0,0,0.25,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,200,200);closePath();fillStyle=green;fill();restore();restore();' + ); + }); - var trace = layer.getContext().getTrace(); + test('optional disable buffer canvas', function() { + var stage = addStage(); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(0.25,0,0,0.25,100,100);save();shadowColor=rgba(0,0,0,1);shadowBlur=0;shadowOffsetX=5;shadowOffsetY=5;beginPath();rect(0,0,200,200);closePath();fillStyle=green;fill();restore();restore();'); + var layer = new Konva.Layer(); + + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + opacity: 0.5, + perfectDrawEnabled: false }); + layer.add(rect); - test('optional disable buffer canvas', function() { - var stage = addStage(); + stage.add(layer); - var layer = new Konva.Layer(); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.globalAlpha = 0.5; + // stroke + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.lineWidth = 10; + context.strokeStyle = 'black'; + context.fillStyle = 'green'; + context.fill(); + context.stroke(); - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - opacity : 0.5, - perfectDrawEnabled : false - }); + compareLayerAndCanvas(layer, canvas, 10); - layer.add(rect); + var trace = layer.getContext().getTrace(); - stage.add(layer); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();' + ); + }); - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.globalAlpha = 0.5; - // stroke - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.lineWidth = 10; - context.strokeStyle = 'black'; - context.fillStyle = 'green'; - context.fill(); - context.stroke(); + // ====================================================== + test('optional disable shadow for stroke', function() { + var stage = addStage(); + var layer = new Konva.Layer(); - compareLayerAndCanvas(layer, canvas, 10); - - var trace = layer.getContext().getTrace(); - - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);globalAlpha=0.5;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;stroke();restore();'); + var rect = new Konva.Rect({ + x: 100, + y: 50, + width: 100, + height: 50, + fill: 'green', + stroke: 'black', + strokeWidth: 10, + shadowColor: 'grey', + shadowBlur: 10, + shadowOffset: { + x: 20, + y: 20 + }, + shadowForStrokeEnabled: false }); - // ====================================================== - test('optional disable shadow for stroke', function(){ - var stage = addStage(); + layer.add(rect); + stage.add(layer); - var layer = new Konva.Layer(); + var canvas = createCanvas(); + var context = canvas.getContext('2d'); + context.beginPath(); + context.rect(100, 50, 100, 50); + context.closePath(); + context.fillStyle = 'green'; + context.shadowColor = 'grey'; + context.shadowBlur = 10 * canvas.ratio; + context.shadowOffsetX = 20 * canvas.ratio; + context.shadowOffsetY = 20 * canvas.ratio; + context.lineWidth = 10; + context.fill(); - var rect = new Konva.Rect({ - x: 100, - y: 50, - width: 100, - height: 50, - fill: 'green', - stroke: 'black', - strokeWidth: 10, - shadowColor: 'grey', - shadowBlur: 10, - shadowOffset: { - x: 20, - y: 20 - }, - shadowForStrokeEnabled : false - }); + context.shadowColor = 'rgba(0,0,0, 0)'; + context.stroke(); - layer.add(rect); - stage.add(layer); + compareLayerAndCanvas(layer, canvas, 10); - var canvas = createCanvas(); - var context = canvas.getContext('2d'); - context.beginPath(); - context.rect(100, 50, 100, 50); - context.closePath(); - context.fillStyle = 'green'; - context.shadowColor = 'grey'; - context.shadowBlur = 10 * canvas.ratio; - context.shadowOffsetX = 20 * canvas.ratio; - context.shadowOffsetY = 20 * canvas.ratio; - context.lineWidth = 10; - context.fill(); + var trace = layer.getContext().getTrace(); + //console.log(trace); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;shadowColor=rgba(0,0,0,0);stroke();restore();restore();' + ); + }); - context.shadowColor = 'rgba(0,0,0, 0)'; - context.stroke(); + test('clone custom shape', function() { + var className = 'myCustomName'; + var CustomShape = function() { + CustomShape.super.apply(this, arguments); + this.className = className; + }; - compareLayerAndCanvas(layer, canvas, 10); + CustomShape.prototype.foo = function() {}; + Konva.Util.extend(CustomShape, Konva.Shape); - var trace = layer.getContext().getTrace(); - //console.log(trace); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,100,50);save();shadowColor=rgba(128,128,128,1);shadowBlur=10;shadowOffsetX=20;shadowOffsetY=20;beginPath();rect(0,0,100,50);closePath();fillStyle=green;fill();lineWidth=10;strokeStyle=black;shadowColor=rgba(0,0,0,0);stroke();restore();restore();'); + var myShape = new CustomShape({ + fill: 'grey' }); + var clone = myShape.clone(); + assert.equal(clone instanceof CustomShape, true); + assert.equal(clone instanceof Konva.Shape, true); + assert.equal(clone.className, className); + assert.equal(clone.fill(), 'grey'); + assert.equal(clone.foo, CustomShape.prototype.foo); + }); - test('clone custom shape', function() { - var className = 'myCustomName' - var CustomShape = function() { - CustomShape.super.apply(this, arguments); - this.className = className; - } - - CustomShape.prototype.foo = function() {}; - - Konva.Util.extend(CustomShape, Konva.Shape); - - var myShape = new CustomShape({ - fill : 'grey' - }); - - var clone = myShape.clone(); - assert.equal(clone instanceof CustomShape, true); - assert.equal(clone instanceof Konva.Shape, true); - assert.equal(clone.className, className); - assert.equal(clone.fill(), 'grey'); - assert.equal(clone.foo, CustomShape.prototype.foo); + test('getClientRect should skip disabled attributes', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var shape = new Konva.Rect({ + x: 200, + y: 100, + width: 100, + height: 100, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + strokeEnabled: false, + shadowOffsetX: 10, + shadowEnabled: false }); - test('getClientRect should skip disabled attributes', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var shape = new Konva.Rect({ - x: 200, - y: 100, - width: 100, - height: 100, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - strokeEnabled: false, - shadowOffsetX: 10, - shadowEnabled: false - }); + layer.add(shape); + stage.add(layer); - layer.add(shape); - stage.add(layer); + var rect = shape.getClientRect(); - var rect = shape.getClientRect(); + assert.equal(rect.width, 100, 'should not effect width'); + assert.equal(rect.height, 100, 'should not effect width'); + }); - assert.equal(rect.width, 100, 'should not effect width'); - assert.equal(rect.height, 100, 'should not effect width'); + test('shadow should respect pixel ratio', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + layer.getCanvas().setPixelRatio(2); + var shape = new Konva.Rect({ + width: 100, + height: 100, + fill: 'black', + shadowColor: 'green', + shadowOffsetX: 20, + shadowOffsetY: 20, + shadowBlur: 0 }); + layer.add(shape); + stage.add(layer); + var data = layer.getContext().getImageData(15 * 2, (100 + 5) * 2, 1, 1); + assert.equal(data.data[3], 0, 'pixel should be empty, no shadow here'); + }); - test('shadow should respect pixel ratio', function() { + test('text shadow blur should take scale into account', function() { + var stage = addStage(); + var layer1 = new Konva.Layer(); + stage.add(layer1); - var stage = addStage(); - var layer = new Konva.Layer(); - layer.getCanvas().setPixelRatio(2); - var shape = new Konva.Rect({ - width: 100, - height: 100, - fill: 'black', - shadowColor: 'green', - shadowOffsetX: 20, - shadowOffsetY: 20, - shadowBlur: 0 - }); - - layer.add(shape); - stage.add(layer); - var data = layer.getContext().getImageData(15 * 2, (100 + 5) * 2, 1, 1); - assert.equal(data.data[3], 0, 'pixel should be empty, no shadow here'); + var rect1 = new Konva.Rect({ + x: 10, + y: 10, + scaleX: 0.5, + scaleY: 0.5, + width: 80, + height: 80, + fill: 'black', + shadowColor: 'black', + shadowOffsetX: 0, + shadowOffsetY: 50, + shadowBlur: 10 }); + layer1.add(rect1); + stage.add(layer1); + var layer2 = new Konva.Layer(); + stage.add(layer2); - test('text shadow blur should take scale into account', function() { - var stage = addStage(); - var layer1 = new Konva.Layer(); - stage.add(layer1); - - var rect1 = new Konva.Rect({ - x: 10, - y: 10, - scaleX: 0.5, - scaleY: 0.5, - width: 80, height: 80, - fill: 'black', - shadowColor: 'black', - shadowOffsetX: 0, - shadowOffsetY: 50, - shadowBlur: 10 - }); - layer1.add(rect1); - stage.add(layer1); - - - var layer2 = new Konva.Layer(); - stage.add(layer2); - - var rect2 = new Konva.Rect({ - x: 10, - y: 10, - fill: 'black', - width: 40, height: 40, - shadowColor: 'black', - shadowOffsetX: 0, - shadowOffsetY: 25, - shadowBlur: 5 - }); - layer2.add(rect2); - stage.add(layer2); - - if (!window.isPhantomJS) { - compareLayers(layer1, layer2, 30); - } + var rect2 = new Konva.Rect({ + x: 10, + y: 10, + fill: 'black', + width: 40, + height: 40, + shadowColor: 'black', + shadowOffsetX: 0, + shadowOffsetY: 25, + shadowBlur: 5 }); + layer2.add(rect2); + stage.add(layer2); - + if (!window.isPhantomJS) { + compareLayers(layer1, layer2, 30); + } + }); }); diff --git a/test/unit/Stage-test.js b/test/unit/Stage-test.js index 572c6d2b..17eaa2a7 100644 --- a/test/unit/Stage-test.js +++ b/test/unit/Stage-test.js @@ -1,633 +1,820 @@ suite('Stage', function() { + // ====================================================== + test('instantiate stage with id', function() { + var container = Konva.document.createElement('div'); + container.id = 'container'; - // ====================================================== - test('instantiate stage with id', function() { - var container = Konva.document.createElement('div'); - container.id = 'container'; + konvaContainer.appendChild(container); - konvaContainer.appendChild(container); + var stage = new Konva.Stage({ + container: 'container', + width: 578, + height: 200 + }); - var stage = new Konva.Stage({ - container: 'container', - width: 578, - height: 200 + assert.equal(stage.getContent().className, 'konvajs-content'); + assert.equal(stage.getContent().getAttribute('role'), 'presentation'); + }); + + // ====================================================== + test('test stage buffer canvas and hit buffer canvas', function() { + var container = Konva.document.createElement('div'); + container.id = 'container'; + + konvaContainer.appendChild(container); + + // simulate pixelRatio = 2 + Konva.pixelRatio = 2; + + var stage = new Konva.Stage({ + container: 'container', + width: 578, + height: 200 + }); + + assert.equal(stage.bufferCanvas.getPixelRatio(), 2); + assert.equal(stage.bufferHitCanvas.getPixelRatio(), 1); + + // reset + Konva.pixelRatio = 1; + }); + + // ====================================================== + test('instantiate stage with dom element', function() { + var container = Konva.document.createElement('div'); + + konvaContainer.appendChild(container); + + var stage = new Konva.Stage({ + container: container, + width: 578, + height: 200 + }); + }); + + // ====================================================== + test('stage instantiation should clear container', function() { + var container = Konva.document.createElement('div'); + var dummy = Konva.document.createElement('p'); + + container.appendChild(dummy); + konvaContainer.appendChild(container); + + var stage = new Konva.Stage({ + container: container, + width: 578, + height: 200 + }); + + assert.equal( + container.getElementsByTagName('p').length, + 0, + 'container should have no p tags' + ); + }); + + // ====================================================== + test('test stage cloning', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + + var stageClone = stage.clone(); + assert.notEqual( + stage.getContainer(), + stageClone.getContainer(), + 'clone should be in different container' + ); + + assert.equal( + stage.getContainer().childNodes[0].childNodes.length, + 1, + 'container should not have changes' + ); + }); + + // ====================================================== + test('set stage size', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + name: 'myCircle' + }); + + assert.equal(stage.getSize().width, 578); + assert.equal(stage.getSize().height, 200); + stage.setSize({ width: 1, height: 2 }); + assert.equal(stage.getSize().width, 1); + assert.equal(stage.getSize().height, 2); + stage.setSize({ width: 3, height: 3 }); + assert.equal(stage.getSize().width, 3); + assert.equal(stage.getSize().height, 3); + stage.setSize({ + width: 4, + height: 5 + }); + assert.equal(stage.getSize().width, 4); + assert.equal(stage.getSize().height, 5); + stage.setWidth(6); + assert.equal(stage.getSize().width, 6); + assert.equal(stage.getSize().height, 5); + stage.setHeight(7); + assert.equal(stage.getSize().width, 6); + assert.equal(stage.getSize().height, 7); + stage.setSize({ width: 8, height: 9 }); + assert.equal(stage.getSize().width, 8); + assert.equal(stage.getSize().height, 9); + stage.setSize({ width: 10, height: 11 }); + assert.equal(stage.getSize().width, 10); + assert.equal(stage.getSize().height, 11); + + layer.add(circle); + stage.add(layer); + + stage.setSize({ width: 333, height: 155 }); + + assert.equal(stage.getSize().width, 333); + assert.equal(stage.getSize().height, 155); + assert.equal(stage.getContent().style.width, '333px'); + assert.equal(stage.getContent().style.height, '155px'); + assert.equal( + layer.getCanvas()._canvas.width, + 333 * layer.getCanvas().getPixelRatio() + ); + assert.equal( + layer.getCanvas()._canvas.height, + 155 * layer.getCanvas().getPixelRatio() + ); + }); + + // ====================================================== + test('get stage DOM', function() { + var stage = addStage(); + + assert.equal(stage.getContent().className, 'konvajs-content'); + }); + + // ====================================================== + test('stage getIntersection()', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle' + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle' + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + assert.equal( + stage.getIntersection({ x: 300, y: 100 }).getId(), + 'greenCircle', + 'shape should be greenCircle' + ); + assert.equal( + stage.getIntersection({ x: 380, y: 100 }).getId(), + 'redCircle', + 'shape should be redCircle' + ); + assert.equal( + stage.getIntersection({ x: 100, y: 100 }), + null, + 'shape should be null' + ); + }); + + // ====================================================== + test('layer getIntersection() with selector', function() { + var stage = addStage(); + var layer = new Konva.Layer({ + name: 'layer' + }); + + var group = new Konva.Group({ + name: 'group' + }); + + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black' + }); + + group.add(circle); + layer.add(group); + stage.add(layer); + + assert.equal( + stage.getIntersection( + { x: stage.width() / 2, y: stage.height() / 2 }, + 'Circle' + ), + circle, + 'intersection with shape selector' + ); + assert.equal( + stage.getIntersection( + { x: stage.width() / 2, y: stage.height() / 2 }, + '.group' + ), + group, + 'intersection with group selector' + ); + assert.equal( + stage.getIntersection( + { x: stage.width() / 2, y: stage.height() / 2 }, + 'Stage' + ), + stage, + 'intersection with stage selector' + ); + }); + + // ====================================================== + test('stage getIntersection() edge detection', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle' + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle' + }); + + stage.on('contentMousemove', function() { + var pos = stage.getPointerPosition(); + var shape = stage.getIntersection(pos); + if (!shape) { + //console.log(pos); + } + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + assert.equal( + stage.getIntersection({ x: 370, y: 93 }).getId(), + 'greenCircle', + 'shape should be greenCircle' + ); + // TODO: this passes in the browser but fails in phantomjs. no idea why. + //assert.equal(stage.getIntersection(371, 93).getId(), 'greenCircle', 'shape should be greenCircle'); + assert.equal( + stage.getIntersection({ x: 372, y: 93 }).getId(), + 'redCircle', + 'shape should be redCircle' + ); + + //console.log(layer.hitCanvas.context._context.getImageData(1, 1, 1, 1).data) + }); + + // ====================================================== + test('test getAllIntersections', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var redCircle = new Konva.Circle({ + x: 380, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle' + }); + + var greenCircle = new Konva.Circle({ + x: 300, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle' + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + // test individual shapes + assert.equal( + stage.getAllIntersections({ x: 266, y: 114 }).length, + 1, + '17) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 266, y: 114 })[0].getId(), + 'greenCircle', + '19) first intersection should be greenCircle' + ); + + assert.equal( + stage.getAllIntersections({ x: 414, y: 115 }).length, + 1, + '18) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 414, y: 115 })[0].getId(), + 'redCircle', + '20) first intersection should be redCircle' + ); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '1) getAllIntersections should return two shapes' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '2) first intersection should be redCircle' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '3) second intersection should be greenCircle' + ); + + // hide green circle. make sure only red circle is in result set + greenCircle.hide(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 1, + '4) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '5) first intersection should be redCircle' + ); + + // show green circle again. make sure both circles are in result set + greenCircle.show(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '6) getAllIntersections should return two shapes' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '7) first intersection should be redCircle' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '8) second intersection should be greenCircle' + ); + + // hide red circle. make sure only green circle is in result set + redCircle.hide(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 1, + '9) getAllIntersections should return one shape' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'greenCircle', + '10) first intersection should be greenCircle' + ); + + // show red circle again. make sure both circles are in result set + redCircle.show(); + layer.draw(); + + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '11) getAllIntersections should return two shapes' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '12) first intersection should be redCircle' + ); + assert.equal( + stage.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '13) second intersection should be greenCircle' + ); + + // test from layer + assert.equal( + layer.getAllIntersections({ x: 350, y: 118 }).length, + 2, + '14) getAllIntersections should return two shapes' + ); + assert.equal( + layer.getAllIntersections({ x: 350, y: 118 })[0].getId(), + 'redCircle', + '15) first intersection should be redCircle' + ); + assert.equal( + layer.getAllIntersections({ x: 350, y: 118 })[1].getId(), + 'greenCircle', + '16) second intersection should be greenCircle' + ); + }); + + // ====================================================== + test('test getAllIntersections for text', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + + var text = new Konva.Text({ + x: 0, + y: 0, + text: 'Hello world', + fontSize: 50, + name: 'intersectText' + }); + + layer.add(text); + stage.add(layer); + + // test individual shapes + assert.equal( + stage.getAllIntersections({ x: 10, y: 10 }).length, + 1, + '17) getAllIntersections should return one shape' + ); + }); + + // ====================================================== + test('scale stage after add layer', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4 + }); + + layer.add(circle); + stage.add(layer); + + stage.setScale({ x: 0.5, y: 0.5 }); + + assert.equal(stage.getScale().x, 0.5, 'stage scale x should be 0.5'); + assert.equal(stage.getScale().y, 0.5, 'stage scale y should be 0.5'); + stage.draw(); + }); + + // ====================================================== + test('scale stage before add shape', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4 + }); + + stage.setScale({ x: 0.5, y: 0.5 }); + + assert.equal(stage.getScale().x, 0.5, 'stage scale x should be 0.5'); + assert.equal(stage.getScale().y, 0.5, 'stage scale y should be 0.5'); + + layer.add(circle); + stage.add(layer); + }); + + // ====================================================== + test('remove stage', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4 + }); + + layer.add(circle); + stage.add(layer); + + // remove should have no effect, and should cause no JS error + stage.remove(); + }); + + // ====================================================== + test('destroy stage', function() { + var container = Konva.document.createElement('div'); + + konvaContainer.appendChild(container); + + var stage = new Konva.Stage({ + container: container, + width: 578, + height: 200, + id: 'stageFalconId', + name: 'stageFalconName' + }); + + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.getWidth() / 2, + y: stage.getHeight() / 2, + radius: 70, + fill: 'green', + stroke: 'black', + strokeWidth: 4, + id: 'circleFalconId', + name: 'circleFalconName' + }); + + layer.add(circle); + stage.add(layer); + + assert.equal( + Konva.ids.stageFalconId._id, + stage._id, + 'stage id should be in global ids map' + ); + assert.equal( + Konva.names.stageFalconName[0]._id, + stage._id, + 'stage name should be in global names map' + ); + assert.equal( + Konva.ids.circleFalconId._id, + circle._id, + 'circle id should be in global ids map' + ); + assert.equal( + Konva.names.circleFalconName[0]._id, + circle._id, + 'circle name should be in global names map' + ); + + stage.destroy(); + + assert.equal( + Konva.ids.stageFalconId, + undefined, + 'stage should no longer be in ids map' + ); + assert.equal( + Konva.names.stageFalconName, + undefined, + 'stage should no longer be in names map' + ); + assert.equal( + Konva.ids.circleFalconId, + undefined, + 'circle should no longer be in ids map' + ); + assert.equal( + Konva.names.circleFalconName, + undefined, + 'circle should no longer be in names map' + ); + assert.equal( + Konva.stages.indexOf(stage) === -1, + true, + 'stage should not be in stages array' + ); + }); + + // ====================================================== + test('scale stage with no shapes', function() { + var stage = addStage(); + + var layer = new Konva.Layer(); + + stage.add(layer); + stage.setScale(0.5); + + stage.draw(); + }); + + // ====================================================== + test('test stage.getStage()', function() { + var stage = addStage(); + + assert.notEqual(stage.getStage(), undefined); + + //console.log(stage.getStage()); + }); + + test('add multiple layers to stage', function() { + var stage = addStage(); + var layer1 = new Konva.Layer(); + var layer2 = new Konva.Layer(); + var layer3 = new Konva.Layer(); + stage.add(layer1, layer2, layer3); + assert.equal(stage.getLayers().length, 3, 'stage has exactly three layers'); + }); + // ====================================================== + test('test drag and click', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var rect = new Konva.Rect({ + x: 50, + y: 50, + width: 50, + height: 50, + fill: 'red', + draggable: true + }); + + layer.add(rect); + stage.add(layer); + + rect.on('dblclick', function() { + assert(false, 'double click fired'); + }); + + var y = 60; + + // simulate dragging + stage.simulateMouseDown({ + x: 60, + y: y + }); + + stage.simulateMouseMove({ + x: 61, + y: y + }); + + stage.simulateMouseMove({ + x: 62, + y: y + }); + + stage.simulateMouseMove({ + x: 63, + y: y + }); + + stage.simulateMouseMove({ + x: 64, + y: y + }); + + stage.simulateMouseUp({ + x: 65, + y: y + }); + + assert.equal(Konva.isDragging(), false); + assert.equal(Konva.DD.node, undefined); + // simulate click + stage.simulateMouseDown({ + x: 66, + y: y + }); + + stage.simulateMouseUp({ + x: 66, + y: y + }); + assert.equal(Konva.isDragging(), false); + assert.equal(Konva.DD.node, undefined); + }); + + test.skip('toDataURL + HDPI', function(done) { + Konva.pixelRatio = 2; + + var stage = addStage(); + var layer = new Konva.Layer(); + + var image = new Image(); + image.onload = function() { + var lion = new Konva.Image({ + image: image, + draggable: true }); - assert.equal(stage.getContent().className, 'konvajs-content'); - assert.equal(stage.getContent().getAttribute('role'), 'presentation'); + lion.cache(); + lion.drawHitFromCache(); - - }); - - // ====================================================== - test('test stage buffer canvas and hit buffer canvas', function() { - var container = Konva.document.createElement('div'); - container.id = 'container'; - - konvaContainer.appendChild(container); - - // simulate pixelRatio = 2 - Konva.pixelRatio = 2; - - var stage = new Konva.Stage({ - container: 'container', - width: 578, - height: 200 - }); - - - assert.equal(stage.bufferCanvas.getPixelRatio(), 2); - assert.equal(stage.bufferHitCanvas.getPixelRatio(), 1); - - // reset - Konva.pixelRatio = 1; - }); - - // ====================================================== - test('instantiate stage with dom element', function() { - var container = Konva.document.createElement('div'); - - konvaContainer.appendChild(container); - - var stage = new Konva.Stage({ - container: container, - width: 578, - height: 200 - }); - }); - - // ====================================================== - test('stage instantiation should clear container', function() { - var container = Konva.document.createElement('div'); - var dummy = Konva.document.createElement('p'); - - container.appendChild(dummy); - konvaContainer.appendChild(container); - - var stage = new Konva.Stage({ - container: container, - width: 578, - height: 200 - }); - - assert.equal(container.getElementsByTagName('p').length, 0, 'container should have no p tags'); - }); - - // ====================================================== - test('test stage cloning', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - stage.add(layer); - - var stageClone = stage.clone(); - assert.notEqual(stage.getContainer(), stageClone.getContainer(), 'clone should be in different container'); - - assert.equal(stage.getContainer().childNodes[0].childNodes.length, 1, 'container should not have changes'); - - }); - - // ====================================================== - test('set stage size', function() { - var stage = addStage(); - - var layer = new Konva.Layer(); - - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - name: 'myCircle' - }); - - assert.equal(stage.getSize().width, 578); - assert.equal(stage.getSize().height, 200); - stage.setSize({width:1, height:2}); - assert.equal(stage.getSize().width, 1); - assert.equal(stage.getSize().height, 2); - stage.setSize({width: 3, height: 3}); - assert.equal(stage.getSize().width, 3); - assert.equal(stage.getSize().height, 3); - stage.setSize({ - width: 4, - height: 5 - }); - assert.equal(stage.getSize().width, 4); - assert.equal(stage.getSize().height, 5); - stage.setWidth(6); - assert.equal(stage.getSize().width, 6); - assert.equal(stage.getSize().height, 5); - stage.setHeight(7); - assert.equal(stage.getSize().width, 6); - assert.equal(stage.getSize().height, 7); - stage.setSize({width: 8, height: 9}); - assert.equal(stage.getSize().width, 8); - assert.equal(stage.getSize().height, 9); - stage.setSize({width:10, height:11}); - assert.equal(stage.getSize().width, 10); - assert.equal(stage.getSize().height, 11); - - layer.add(circle); - stage.add(layer); - - stage.setSize({width:333, height:155}); - - assert.equal(stage.getSize().width, 333); - assert.equal(stage.getSize().height, 155); - assert.equal(stage.getContent().style.width, '333px'); - assert.equal(stage.getContent().style.height, '155px'); - assert.equal(layer.getCanvas()._canvas.width, 333 * layer.getCanvas().getPixelRatio()); - assert.equal(layer.getCanvas()._canvas.height, 155 * layer.getCanvas().getPixelRatio()); - }); - - // ====================================================== - test('get stage DOM', function() { - var stage = addStage(); - - assert.equal(stage.getContent().className, 'konvajs-content'); - }); - - // ====================================================== - test('stage getIntersection()', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - id: 'redCircle' - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - id: 'greenCircle' - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - assert.equal(stage.getIntersection({x:300, y:100}).getId(), 'greenCircle', 'shape should be greenCircle'); - assert.equal(stage.getIntersection({x:380, y:100}).getId(), 'redCircle', 'shape should be redCircle'); - assert.equal(stage.getIntersection({x:100, y:100}), null, 'shape should be null'); - }); - - // ====================================================== - test('layer getIntersection() with selector', function() { - var stage = addStage(); - var layer = new Konva.Layer({ - name: 'layer' - }); - - var group = new Konva.Group({ - name: 'group' - }); - - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - }); - - group.add(circle) - layer.add(group); - stage.add(layer); - - assert.equal(stage.getIntersection({x: stage.width() / 2, y: stage.height() / 2}, 'Circle'), circle, 'intersection with shape selector'); - assert.equal(stage.getIntersection({x: stage.width() / 2, y: stage.height() / 2}, '.group'), group, 'intersection with group selector'); - assert.equal(stage.getIntersection({x: stage.width() / 2, y: stage.height() / 2}, 'Stage'), stage, 'intersection with stage selector'); - }); - - // ====================================================== - test('stage getIntersection() edge detection', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - id: 'redCircle' - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - id: 'greenCircle' - }); - - stage.on('contentMousemove', function() { - var pos = stage.getPointerPosition(); - var shape = stage.getIntersection(pos); - if (!shape){ - //console.log(pos); - } - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - assert.equal(stage.getIntersection({x:370, y:93}).getId(), 'greenCircle', 'shape should be greenCircle'); - // TODO: this passes in the browser but fails in phantomjs. no idea why. - //assert.equal(stage.getIntersection(371, 93).getId(), 'greenCircle', 'shape should be greenCircle'); - assert.equal(stage.getIntersection({x:372, y:93}).getId(), 'redCircle', 'shape should be redCircle'); - - //console.log(layer.hitCanvas.context._context.getImageData(1, 1, 1, 1).data) - - - }); - - // ====================================================== - test('test getAllIntersections', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - - var redCircle = new Konva.Circle({ - x: 380, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'red', - stroke: 'black', - id: 'redCircle' - }); - - var greenCircle = new Konva.Circle({ - x: 300, - y: stage.getHeight() / 2, - radius: 70, - strokeWidth: 4, - fill: 'green', - stroke: 'black', - id: 'greenCircle' - }); - - layer.add(redCircle); - layer.add(greenCircle); - stage.add(layer); - - // test individual shapes - assert.equal(stage.getAllIntersections({x: 266, y:114}).length, 1, '17) getAllIntersections should return one shape'); - assert.equal(stage.getAllIntersections({x: 266, y:114})[0].getId(), 'greenCircle', '19) first intersection should be greenCircle'); - - assert.equal(stage.getAllIntersections({x: 414, y:115}).length, 1, '18) getAllIntersections should return one shape'); - assert.equal(stage.getAllIntersections({x: 414, y:115})[0].getId(), 'redCircle', '20) first intersection should be redCircle'); - - assert.equal(stage.getAllIntersections({x: 350, y:118}).length, 2, '1) getAllIntersections should return two shapes'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[0].getId(), 'redCircle', '2) first intersection should be redCircle'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[1].getId(), 'greenCircle', '3) second intersection should be greenCircle'); - - // hide green circle. make sure only red circle is in result set - greenCircle.hide(); - layer.draw(); - - assert.equal(stage.getAllIntersections({x: 350, y:118}).length, 1, '4) getAllIntersections should return one shape'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[0].getId(), 'redCircle', '5) first intersection should be redCircle'); - - // show green circle again. make sure both circles are in result set - greenCircle.show(); - layer.draw(); - - assert.equal(stage.getAllIntersections({x: 350, y:118}).length, 2, '6) getAllIntersections should return two shapes'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[0].getId(), 'redCircle', '7) first intersection should be redCircle'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[1].getId(), 'greenCircle', '8) second intersection should be greenCircle'); - - // hide red circle. make sure only green circle is in result set - redCircle.hide(); - layer.draw(); - - assert.equal(stage.getAllIntersections({x: 350, y:118}).length, 1, '9) getAllIntersections should return one shape'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[0].getId(), 'greenCircle', '10) first intersection should be greenCircle'); - - // show red circle again. make sure both circles are in result set - redCircle.show(); - layer.draw(); - - assert.equal(stage.getAllIntersections({x: 350, y:118}).length, 2, '11) getAllIntersections should return two shapes'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[0].getId(), 'redCircle', '12) first intersection should be redCircle'); - assert.equal(stage.getAllIntersections({x: 350, y:118})[1].getId(), 'greenCircle', '13) second intersection should be greenCircle'); - - // test from layer - assert.equal(layer.getAllIntersections({x: 350, y:118}).length, 2, '14) getAllIntersections should return two shapes'); - assert.equal(layer.getAllIntersections({x: 350, y:118})[0].getId(), 'redCircle', '15) first intersection should be redCircle'); - assert.equal(layer.getAllIntersections({x: 350, y:118})[1].getId(), 'greenCircle', '16) second intersection should be greenCircle'); - - }); - - // ====================================================== - test('scale stage after add layer', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4 - }); - - layer.add(circle); - stage.add(layer); - - stage.setScale({x:0.5, y:0.5}); - - assert.equal(stage.getScale().x, 0.5, 'stage scale x should be 0.5'); - assert.equal(stage.getScale().y, 0.5, 'stage scale y should be 0.5'); - stage.draw(); - }); - - // ====================================================== - test('scale stage before add shape', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4 - }); - - stage.setScale({x:0.5, y:0.5}); - - assert.equal(stage.getScale().x, 0.5, 'stage scale x should be 0.5'); - assert.equal(stage.getScale().y, 0.5, 'stage scale y should be 0.5'); - - layer.add(circle); - stage.add(layer); - }); - - // ====================================================== - test('remove stage', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4 - }); - - layer.add(circle); - stage.add(layer); - - // remove should have no effect, and should cause no JS error - stage.remove(); - - - }); - - // ====================================================== - test('destroy stage', function() { - var container = Konva.document.createElement('div'); - - konvaContainer.appendChild(container); - - var stage = new Konva.Stage({ - container: container, - width: 578, - height: 200, - id: 'stageFalconId', - name: 'stageFalconName' - }); - - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.getWidth() / 2, - y: stage.getHeight() / 2, - radius: 70, - fill: 'green', - stroke: 'black', - strokeWidth: 4, - id: 'circleFalconId', - name: 'circleFalconName' - }); - - layer.add(circle); - stage.add(layer); - - assert.equal(Konva.ids.stageFalconId._id, stage._id, 'stage id should be in global ids map'); - assert.equal(Konva.names.stageFalconName[0]._id, stage._id, 'stage name should be in global names map'); - assert.equal(Konva.ids.circleFalconId._id, circle._id, 'circle id should be in global ids map'); - assert.equal(Konva.names.circleFalconName[0]._id, circle._id, 'circle name should be in global names map'); - - stage.destroy(); - - assert.equal(Konva.ids.stageFalconId, undefined, 'stage should no longer be in ids map'); - assert.equal(Konva.names.stageFalconName, undefined, 'stage should no longer be in names map'); - assert.equal(Konva.ids.circleFalconId, undefined, 'circle should no longer be in ids map'); - assert.equal(Konva.names.circleFalconName, undefined, 'circle should no longer be in names map'); - assert.equal(Konva.stages.indexOf(stage) === -1, true, 'stage should not be in stages array'); - - - }); - - // ====================================================== - test('scale stage with no shapes', function() { - var stage = addStage(); - - var layer = new Konva.Layer(); - - stage.add(layer); - stage.setScale(0.5); - - stage.draw(); - }); - - // ====================================================== - test('test stage.getStage()', function() { - var stage = addStage(); - - assert.notEqual(stage.getStage(), undefined); - - //console.log(stage.getStage()); - }); - - test('add multiple layers to stage', function() { - var stage = addStage(); - var layer1 = new Konva.Layer(); - var layer2 = new Konva.Layer(); - var layer3 = new Konva.Layer(); - stage.add(layer1, layer2, layer3); - assert.equal(stage.getLayers().length, 3, 'stage has exactly three layers'); - }); - // ====================================================== - test('test drag and click', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var rect = new Konva.Rect({ - x: 50, - y: 50, - width: 50, - height: 50, - fill: 'red', - draggable: true - }); - - layer.add(rect); - stage.add(layer); - - rect.on('dblclick', function() { - assert(false, 'double click fired'); - }); - - var y = 60; - - // simulate dragging - stage.simulateMouseDown({ - x: 60, - y: y - }); - - stage.simulateMouseMove({ - x: 61, - y: y - }); - - stage.simulateMouseMove({ - x: 62, - y: y - }); - - stage.simulateMouseMove({ - x: 63, - y: y - }); - - stage.simulateMouseMove({ - x: 64, - y: y - }); - - stage.simulateMouseUp({ - x: 65, - y: y - }); - - assert.equal(Konva.isDragging(), false); - assert.equal(Konva.DD.node, undefined); - // simulate click - stage.simulateMouseDown({ - x: 66, - y: y - }); - - stage.simulateMouseUp({ - x: 66, - y: y - }); - assert.equal(Konva.isDragging(), false); - assert.equal(Konva.DD.node, undefined); - }); - - test.skip('toDataURL + HDPI', function(done) { - Konva.pixelRatio = 2; - - var stage = addStage(); - var layer = new Konva.Layer(); - - var image = new Image(); - image.onload = function() { - var lion = new Konva.Image({ - image: image, - draggable: true - }); - - lion.cache(); - lion.drawHitFromCache(); - - layer.add(lion); - stage.add(layer); - stage.draw(); - - var snapshotStage = addStage(); - - stage.toImage({ - callback: function (image) { - var imageNode = new Konva.Image({ - image: image - }); - var snapshotLayer = new Konva.Layer(); - snapshotLayer.add(imageNode); - snapshotStage.add(snapshotLayer); - snapshotStage.draw(); - Konva.pixelRatio = undefined; - done(); - } - }); - }; - image.src = 'assets/lion.png'; - }); - - test('toDataURL in sync way', function() { - var stage = addStage(); - var layer = new Konva.Layer(); - var circle = new Konva.Circle({ - x: stage.width() / 2, - y: stage.height() / 2, - fill: 'red', - radius: 50 - }); - layer.add(circle); - stage.add(layer); - assert.equal(stage.toDataURL(), layer.toDataURL()); - }); - - test('check hit graph with stage listeting property', function() { - var stage = addStage(); - var layer = new Konva.Layer(); + layer.add(lion); stage.add(layer); - showHit(layer); - var circle = new Konva.Circle({ - fill: 'green', - radius: 50 + stage.draw(); + + var snapshotStage = addStage(); + + stage.toImage({ + callback: function(image) { + var imageNode = new Konva.Image({ + image: image + }); + var snapshotLayer = new Konva.Layer(); + snapshotLayer.add(imageNode); + snapshotStage.add(snapshotLayer); + snapshotStage.draw(); + Konva.pixelRatio = undefined; + done(); + } }); - layer.add(circle); + }; + image.src = 'assets/lion.png'; + }); - var pos = { - x: stage.width() / 2, - y: stage.height() /2 - }; - circle.position(pos); - stage.draw(); - - // try to detect circle via hit graph - assert.equal(stage.getIntersection(pos), circle, 'has circle'); - - // disable hit graph - stage.listening(false); - stage.draw(); - assert.equal(!!stage.getIntersection(pos), false, 'no circle'); - - // enable it again - stage.listening(true); - stage.draw(); - assert.equal(stage.getIntersection(pos), circle, 'circle again'); + test('toDataURL in sync way', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + var circle = new Konva.Circle({ + x: stage.width() / 2, + y: stage.height() / 2, + fill: 'red', + radius: 50 }); + layer.add(circle); + stage.add(layer); + assert.equal(stage.toDataURL(), layer.toDataURL()); + }); + + test('check hit graph with stage listeting property', function() { + var stage = addStage(); + var layer = new Konva.Layer(); + stage.add(layer); + showHit(layer); + var circle = new Konva.Circle({ + fill: 'green', + radius: 50 + }); + layer.add(circle); + + var pos = { + x: stage.width() / 2, + y: stage.height() / 2 + }; + circle.position(pos); + stage.draw(); + + // try to detect circle via hit graph + assert.equal(stage.getIntersection(pos), circle, 'has circle'); + + // disable hit graph + stage.listening(false); + stage.draw(); + assert.equal(!!stage.getIntersection(pos), false, 'no circle'); + + // enable it again + stage.listening(true); + stage.draw(); + assert.equal(stage.getIntersection(pos), circle, 'circle again'); + }); });