2019-01-02 04:59:27 +08:00
import { Util , Collection } from './Util' ;
2019-01-11 21:51:46 +08:00
import { Factory } from './Factory' ;
2019-03-10 23:31:13 +08:00
import { Container , ContainerConfig } from './Container' ;
2019-03-07 11:19:32 +08:00
import { Konva } from './Global' ;
2019-01-02 04:59:27 +08:00
import { SceneCanvas , HitCanvas } from './Canvas' ;
import { GetSet , Vector2d } from './types' ;
import { Shape } from './Shape' ;
import { BaseLayer } from './BaseLayer' ;
2019-02-24 10:36:05 +08:00
import { DD } from './DragAndDrop' ;
2019-02-27 21:06:04 +08:00
import { _registerNode } from './Global' ;
2019-03-27 04:30:00 +08:00
import * as PointerEvents from './PointerEvents' ;
2019-01-02 04:59:27 +08:00
2019-03-10 23:31:13 +08:00
export interface StageConfig extends ContainerConfig {
container : HTMLDivElement | string ;
}
2019-01-02 04:59:27 +08:00
// CONSTANTS
var STAGE = 'Stage' ,
STRING = 'string' ,
PX = 'px' ,
MOUSEOUT = 'mouseout' ,
MOUSELEAVE = 'mouseleave' ,
MOUSEOVER = 'mouseover' ,
MOUSEENTER = 'mouseenter' ,
MOUSEMOVE = 'mousemove' ,
MOUSEDOWN = 'mousedown' ,
MOUSEUP = 'mouseup' ,
2019-05-28 03:39:17 +08:00
// TODO: add them into "on" method docs and into site docs
2019-03-27 04:30:00 +08:00
POINTERMOVE = 'pointermove' ,
POINTERDOWN = 'pointerdown' ,
POINTERUP = 'pointerup' ,
POINTERCANCEL = 'pointercancel' ,
LOSTPOINTERCAPTURE = 'lostpointercapture' ,
2019-01-02 04:59:27 +08:00
CONTEXTMENU = 'contextmenu' ,
CLICK = 'click' ,
DBL_CLICK = 'dblclick' ,
TOUCHSTART = 'touchstart' ,
TOUCHEND = 'touchend' ,
TAP = 'tap' ,
DBL_TAP = 'dbltap' ,
TOUCHMOVE = 'touchmove' ,
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' ,
2019-04-10 22:28:42 +08:00
CONTENT_POINTERMOVE = 'contentPointermove' ,
CONTENT_POINTERDOWN = 'contentPointerdown' ,
CONTENT_POINTERUP = 'contentPointerup' ,
2019-01-02 04:59:27 +08:00
CONTENT_WHEEL = 'contentWheel' ,
RELATIVE = 'relative' ,
KONVA_CONTENT = 'konvajs-content' ,
SPACE = ' ' ,
UNDERSCORE = '_' ,
CONTAINER = 'container' ,
2019-01-22 21:43:43 +08:00
MAX_LAYERS_NUMBER = 5 ,
2019-01-02 04:59:27 +08:00
EMPTY_STRING = '' ,
EVENTS = [
2019-04-04 09:28:48 +08:00
MOUSEENTER ,
2019-01-02 04:59:27 +08:00
MOUSEDOWN ,
MOUSEMOVE ,
MOUSEUP ,
MOUSEOUT ,
TOUCHSTART ,
TOUCHMOVE ,
TOUCHEND ,
MOUSEOVER ,
WHEEL ,
2019-03-27 04:30:00 +08:00
CONTEXTMENU ,
POINTERDOWN ,
POINTERMOVE ,
POINTERUP ,
POINTERCANCEL ,
LOSTPOINTERCAPTURE
2019-01-02 04:59:27 +08:00
] ,
// cached variables
eventsLength = EVENTS . length ;
function addEvent ( ctx , eventName ) {
ctx . content . addEventListener (
eventName ,
function ( evt ) {
ctx [ UNDERSCORE + eventName ] ( evt ) ;
} ,
false
) ;
}
2019-01-22 21:43:43 +08:00
2019-02-20 09:17:49 +08:00
const NO_POINTERS_MESSAGE = ` Pointer position is missing and not registered by the stage. Looks like it is outside of the stage container. You can set it manually from event: stage.setPointersPositions(event); ` ;
2019-01-02 04:59:27 +08:00
export const stages : Stage [ ] = [ ] ;
2019-02-23 09:48:00 +08:00
function checkNoClip ( attrs : any = { } ) {
if ( attrs . clipFunc || attrs . clipWidth || attrs . clipHeight ) {
Util . warn (
'Stage does not support clipping. Please use clip for Layers or Groups.'
) ;
}
return attrs ;
}
2019-01-02 04:59:27 +08:00
/ * *
* Stage constructor . A stage is used to contain multiple layers
* @constructor
* @memberof Konva
* @augments Konva . Container
* @param { Object } config
* @param { String | Element } config . container Container selector or DOM element
* @ @nodeParams
* @example
* var stage = new Konva . Stage ( {
* width : 500 ,
* height : 800 ,
* container : 'containerId' // or "#containerId" or ".containerClass"
* } ) ;
* /
2019-04-17 23:45:47 +08:00
export class Stage extends Container < BaseLayer > {
2019-01-02 04:59:27 +08:00
content : HTMLDivElement ;
pointerPos : Vector2d | null ;
2019-08-04 15:38:57 +08:00
_pointerPositions : ( Vector2d & { id? : number } ) [ ] = [ ] ;
_changedPointerPositions : ( Vector2d & { id? : number } ) [ ] = [ ] ;
2019-08-04 10:41:57 +08:00
2019-01-02 04:59:27 +08:00
bufferCanvas : SceneCanvas ;
bufferHitCanvas : HitCanvas ;
targetShape : Shape ;
clickStartShape : Shape ;
clickEndShape : Shape ;
dblTimeout : any ;
tapStartShape : Shape ;
2019-03-10 23:31:13 +08:00
constructor ( config : StageConfig ) {
2019-02-23 09:48:00 +08:00
super ( checkNoClip ( config ) ) ;
2019-01-02 04:59:27 +08:00
this . _buildDOM ( ) ;
this . _bindContentEvents ( ) ;
stages . push ( this ) ;
2019-02-20 09:17:49 +08:00
this . on ( 'widthChange.konva heightChange.konva' , this . _resizeDOM ) ;
this . on ( 'visibleChange.konva' , this . _checkVisibility ) ;
2019-02-23 09:48:00 +08:00
this . on (
'clipWidthChange.konva clipHeightChange.konva clipFuncChange.konva' ,
( ) = > {
checkNoClip ( this . attrs ) ;
}
) ;
2019-02-20 09:17:49 +08:00
this . _checkVisibility ( ) ;
2019-01-02 04:59:27 +08:00
}
_validateAdd ( child ) {
2019-02-27 21:06:04 +08:00
const isLayer = child . getType ( ) === 'Layer' ;
const isFastLayer = child . getType ( ) === 'FastLayer' ;
const valid = isLayer || isFastLayer ;
if ( ! valid ) {
2019-01-02 04:59:27 +08:00
Util . throw ( 'You may only add layers to the stage.' ) ;
}
}
2019-02-20 09:17:49 +08:00
_checkVisibility() {
const style = this . visible ( ) ? '' : 'none' ;
this . content . style . display = style ;
}
2019-01-02 04:59:27 +08:00
/ * *
* set container dom element which contains the stage wrapper div element
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # setContainer
2019-01-02 04:59:27 +08:00
* @param { DomElement } container can pass in a dom element or id string
* /
setContainer ( container ) {
if ( typeof container === STRING ) {
if ( container . charAt ( 0 ) === '.' ) {
var className = container . slice ( 1 ) ;
container = document . getElementsByClassName ( className ) [ 0 ] ;
} else {
var id ;
if ( container . charAt ( 0 ) !== '#' ) {
id = container ;
} else {
id = container . slice ( 1 ) ;
}
container = document . getElementById ( id ) ;
}
if ( ! container ) {
throw 'Can not find container in document with id ' + id ;
}
}
this . _setAttr ( CONTAINER , container ) ;
2019-02-20 09:43:06 +08:00
if ( this . content ) {
2019-06-09 13:13:29 +08:00
if ( this . content . parentElement ) {
this . content . parentElement . removeChild ( this . content ) ;
}
2019-02-20 09:43:06 +08:00
container . appendChild ( this . content ) ;
}
2019-01-02 04:59:27 +08:00
return this ;
}
shouldDrawHit() {
return true ;
}
/ * *
* clear all layers
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # clear
2019-01-02 04:59:27 +08:00
* /
clear() {
var layers = this . children ,
len = layers . length ,
n ;
for ( n = 0 ; n < len ; n ++ ) {
layers [ n ] . clear ( ) ;
}
return this ;
}
clone ( obj ) {
if ( ! obj ) {
obj = { } ;
}
2019-03-07 11:19:32 +08:00
obj . container = document . createElement ( 'div' ) ;
2019-01-02 04:59:27 +08:00
return Container . prototype . clone . call ( this , obj ) ;
}
2019-01-06 16:01:20 +08:00
2019-01-02 04:59:27 +08:00
destroy() {
2019-01-06 16:01:20 +08:00
super . destroy ( ) ;
2019-01-02 04:59:27 +08:00
2019-01-06 16:01:20 +08:00
var content = this . content ;
2019-01-02 04:59:27 +08:00
if ( content && Util . _isInDocument ( content ) ) {
this . container ( ) . removeChild ( content ) ;
}
var index = stages . indexOf ( this ) ;
if ( index > - 1 ) {
stages . splice ( index , 1 ) ;
}
return this ;
}
/ * *
2019-11-09 00:37:10 +08:00
* returns absolute pointer position which can be a touch position or mouse position
* pointer position doesn ' t include any transforms ( such as scale ) of the stage
* it is just a plain position of pointer relative to top - left corner of the stage container
2019-01-02 04:59:27 +08:00
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # getPointerPosition
2019-08-22 22:40:05 +08:00
* @returns { Vector2d | null }
2019-01-02 04:59:27 +08:00
* /
2019-08-22 22:40:05 +08:00
getPointerPosition ( ) : Vector2d | null {
2019-09-07 02:39:26 +08:00
const pos = this . _pointerPositions [ 0 ] || this . _changedPointerPositions [ 0 ] ;
2019-08-04 10:41:57 +08:00
if ( ! pos ) {
2019-02-20 09:17:49 +08:00
Util . warn ( NO_POINTERS_MESSAGE ) ;
2019-08-22 22:40:05 +08:00
return null ;
2019-02-20 09:17:49 +08:00
}
2019-08-04 15:38:57 +08:00
return {
x : pos.x ,
y : pos.y
} ;
}
_getPointerById ( id? : number ) {
return this . _pointerPositions . find ( p = > p . id === id ) ;
2019-08-04 10:41:57 +08:00
}
getPointersPositions() {
return this . _pointerPositions ;
2019-01-02 04:59:27 +08:00
}
getStage() {
return this ;
}
getContent() {
return this . content ;
}
_toKonvaCanvas ( config ) {
config = config || { } ;
var x = config . x || 0 ,
y = config . y || 0 ,
canvas = new SceneCanvas ( {
2019-01-06 16:01:20 +08:00
width : config.width || this . width ( ) ,
height : config.height || this . height ( ) ,
2019-01-02 04:59:27 +08:00
pixelRatio : config.pixelRatio || 1
} ) ,
_context = canvas . getContext ( ) . _context ,
layers = this . children ;
if ( x || y ) {
_context . translate ( - 1 * x , - 1 * y ) ;
}
layers . each ( function ( layer ) {
if ( ! layer . isVisible ( ) ) {
return ;
}
var layerCanvas = layer . _toKonvaCanvas ( config ) ;
_context . drawImage (
layerCanvas . _canvas ,
x ,
y ,
layerCanvas . getWidth ( ) / layerCanvas . getPixelRatio ( ) ,
layerCanvas . getHeight ( ) / layerCanvas . getPixelRatio ( )
) ;
} ) ;
return canvas ;
}
/ * *
* get visible intersection shape . This is the preferred
* method for determining if a point intersects a shape or not
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # getIntersection
2019-01-02 04:59:27 +08:00
* @param { Object } pos
* @param { Number } pos . x
* @param { Number } pos . y
* @param { String } [ selector ]
* @returns { Konva . Node }
* @example
* var shape = stage . getIntersection ( { x : 50 , y : 50 } ) ;
* // or if you interested in shape parent:
* var group = stage . getIntersection ( { x : 50 , y : 50 } , 'Group' ) ;
* /
2019-08-22 22:40:05 +08:00
getIntersection ( pos : Vector2d | null , selector? : string ) : Shape | null {
if ( ! pos ) {
return null ;
}
2019-01-02 04:59:27 +08:00
var layers = this . children ,
len = layers . length ,
end = len - 1 ,
n ,
shape ;
for ( n = end ; n >= 0 ; n -- ) {
shape = layers [ n ] . getIntersection ( pos , selector ) ;
if ( shape ) {
return shape ;
}
}
return null ;
}
_resizeDOM() {
if ( this . content ) {
2019-01-06 16:01:20 +08:00
var width = this . width ( ) ,
height = this . height ( ) ,
2019-01-02 04:59:27 +08:00
layers = this . getChildren ( ) ,
len = layers . length ,
n ,
layer ;
// 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 ) ;
// set layer dimensions
for ( n = 0 ; n < len ; n ++ ) {
layer = layers [ n ] ;
layer . setSize ( { width , height } ) ;
layer . draw ( ) ;
}
}
}
add ( layer ) {
if ( arguments . length > 1 ) {
for ( var i = 0 ; i < arguments . length ; i ++ ) {
this . add ( arguments [ i ] ) ;
}
return this ;
}
2019-01-06 16:01:20 +08:00
super . add ( layer ) ;
2019-01-22 21:43:43 +08:00
var length = this . children . length ;
if ( length > MAX_LAYERS_NUMBER ) {
Util . warn (
'The stage has ' +
length +
' layers. Recommended maximin number of layers is 3-5. Adding more layers into the stage may drop the performance. Rethink your tree structure, you can use Konva.Group.'
) ;
}
2019-01-02 04:59:27 +08:00
layer . _setCanvasSize ( this . width ( ) , this . height ( ) ) ;
// draw layer and append canvas to container
layer . draw ( ) ;
2019-03-07 11:19:32 +08:00
if ( Konva . isBrowser ) {
2019-01-02 04:59:27 +08:00
this . content . appendChild ( layer . canvas . _canvas ) ;
}
// chainable
return this ;
}
getParent() {
return null ;
}
getLayer() {
return null ;
}
2019-05-31 05:11:31 +08:00
hasPointerCapture ( pointerId : number ) : boolean {
return PointerEvents . hasPointerCapture ( pointerId , this ) ;
}
setPointerCapture ( pointerId : number ) {
PointerEvents . setPointerCapture ( pointerId , this ) ;
}
releaseCapture ( pointerId : number ) {
PointerEvents . releaseCapture ( pointerId , this ) ;
}
2019-01-02 04:59:27 +08:00
/ * *
* returns a { @link Konva . Collection } of layers
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # getLayers
2019-01-02 04:59:27 +08:00
* /
getLayers() {
return this . getChildren ( ) ;
}
_bindContentEvents() {
2019-03-07 11:19:32 +08:00
if ( ! Konva . isBrowser ) {
2019-01-02 04:59:27 +08:00
return ;
}
for ( var n = 0 ; n < eventsLength ; n ++ ) {
addEvent ( this , EVENTS [ n ] ) ;
}
}
2019-04-04 09:28:48 +08:00
_mouseenter ( evt ) {
this . setPointersPositions ( evt ) ;
this . _fire ( MOUSEENTER , { evt : evt , target : this , currentTarget : this } ) ;
}
2019-01-02 04:59:27 +08:00
_mouseover ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
this . _fire ( CONTENT_MOUSEOVER , { evt : evt } ) ;
2019-02-06 05:43:43 +08:00
this . _fire ( MOUSEOVER , { evt : evt , target : this , currentTarget : this } ) ;
2019-01-02 04:59:27 +08:00
}
_mouseout ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
var targetShape = this . targetShape ;
2019-08-08 17:24:55 +08:00
var eventsEnabled = ! DD . isDragging || Konva . hitOnDragEnabled ;
if ( targetShape && eventsEnabled ) {
2019-01-02 04:59:27 +08:00
targetShape . _fireAndBubble ( MOUSEOUT , { evt : evt } ) ;
targetShape . _fireAndBubble ( MOUSELEAVE , { evt : evt } ) ;
this . targetShape = null ;
2019-08-08 17:24:55 +08:00
} else if ( eventsEnabled ) {
2019-04-04 09:28:48 +08:00
this . _fire ( MOUSELEAVE , {
evt : evt ,
target : this ,
currentTarget : this
} ) ;
this . _fire ( MOUSEOUT , {
evt : evt ,
target : this ,
currentTarget : this
} ) ;
2019-01-02 04:59:27 +08:00
}
this . pointerPos = undefined ;
2019-08-04 10:41:57 +08:00
this . _pointerPositions = [ ] ;
2019-01-02 04:59:27 +08:00
this . _fire ( CONTENT_MOUSEOUT , { evt : evt } ) ;
}
_mousemove ( evt ) {
// workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event
2019-03-07 11:19:32 +08:00
if ( Konva . UA . ieMobile ) {
2019-01-02 04:59:27 +08:00
return this . _touchmove ( evt ) ;
}
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-08-04 15:38:57 +08:00
var pointerId = Util . _getFirstPointerId ( evt ) ;
2019-04-04 09:28:48 +08:00
var shape : Shape ;
2019-01-02 04:59:27 +08:00
2019-08-08 17:24:55 +08:00
var eventsEnabled = ! DD . isDragging || Konva . hitOnDragEnabled ;
if ( eventsEnabled ) {
2019-01-02 04:59:27 +08:00
shape = this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape && shape . isListening ( ) ) {
2019-02-06 05:43:43 +08:00
var differentTarget = ! this . targetShape || this . targetShape !== shape ;
2019-08-08 17:24:55 +08:00
if ( eventsEnabled && differentTarget ) {
2019-01-02 04:59:27 +08:00
if ( this . targetShape ) {
2019-08-04 15:38:57 +08:00
this . targetShape . _fireAndBubble (
MOUSEOUT ,
{ evt : evt , pointerId } ,
shape
) ;
this . targetShape . _fireAndBubble (
MOUSELEAVE ,
{ evt : evt , pointerId } ,
shape
) ;
2019-01-02 04:59:27 +08:00
}
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble (
MOUSEOVER ,
{ evt : evt , pointerId } ,
this . targetShape
) ;
shape . _fireAndBubble (
MOUSEENTER ,
{ evt : evt , pointerId } ,
this . targetShape
) ;
2019-08-08 17:24:55 +08:00
shape . _fireAndBubble ( MOUSEMOVE , { evt : evt , pointerId } ) ;
2019-01-02 04:59:27 +08:00
this . targetShape = shape ;
} else {
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( MOUSEMOVE , { evt : evt , pointerId } ) ;
2019-01-02 04:59:27 +08:00
}
} else {
/ *
* if no shape was detected , clear target shape and try
* to run mouseout from previous target shape
* /
2019-08-08 17:24:55 +08:00
if ( this . targetShape && eventsEnabled ) {
2019-08-04 15:38:57 +08:00
this . targetShape . _fireAndBubble ( MOUSEOUT , { evt : evt , pointerId } ) ;
this . targetShape . _fireAndBubble ( MOUSELEAVE , { evt : evt , pointerId } ) ;
2019-02-06 05:43:43 +08:00
this . _fire ( MOUSEOVER , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId
2019-02-06 05:43:43 +08:00
} ) ;
2019-01-02 04:59:27 +08:00
this . targetShape = null ;
}
this . _fire ( MOUSEMOVE , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId
2019-01-02 04:59:27 +08:00
} ) ;
}
// 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 . cancelable ) {
evt . preventDefault ( ) ;
}
}
_mousedown ( evt ) {
// workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event
2019-03-07 11:19:32 +08:00
if ( Konva . UA . ieMobile ) {
2019-01-02 04:59:27 +08:00
return this . _touchstart ( evt ) ;
}
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-08-04 15:38:57 +08:00
var pointerId = Util . _getFirstPointerId ( evt ) ;
2019-01-02 04:59:27 +08:00
var shape = this . getIntersection ( this . getPointerPosition ( ) ) ;
2019-11-13 04:10:36 +08:00
DD . justDragged = false ;
2019-03-07 11:19:32 +08:00
Konva . listenClickTap = true ;
2019-01-02 04:59:27 +08:00
if ( shape && shape . isListening ( ) ) {
this . clickStartShape = shape ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( MOUSEDOWN , { evt : evt , pointerId } ) ;
2019-01-02 04:59:27 +08:00
} else {
this . _fire ( MOUSEDOWN , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId
2019-01-02 04:59:27 +08:00
} ) ;
}
// content event
this . _fire ( CONTENT_MOUSEDOWN , { evt : evt } ) ;
2019-02-20 09:43:06 +08:00
// Do not prevent default behavior, because it will prevent listening events outside of window iframe
// we used preventDefault for disabling native drag&drop
// but userSelect = none style will do the trick
2019-01-02 04:59:27 +08:00
// if (evt.cancelable) {
2019-02-20 09:43:06 +08:00
// evt.preventDefault();
2019-01-02 04:59:27 +08:00
// }
}
_mouseup ( evt ) {
// workaround for mobile IE to force touch event when unhandled pointer event elevates into a mouse event
2019-03-07 11:19:32 +08:00
if ( Konva . UA . ieMobile ) {
2019-01-02 04:59:27 +08:00
return this . _touchend ( evt ) ;
}
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-08-04 15:38:57 +08:00
var pointerId = Util . _getFirstPointerId ( evt ) ;
2019-01-02 04:59:27 +08:00
var shape = this . getIntersection ( this . getPointerPosition ( ) ) ,
clickStartShape = this . clickStartShape ,
clickEndShape = this . clickEndShape ,
2019-03-07 11:19:32 +08:00
fireDblClick = false ;
2019-01-02 04:59:27 +08:00
2019-03-07 11:19:32 +08:00
if ( Konva . inDblClickWindow ) {
2019-01-02 04:59:27 +08:00
fireDblClick = true ;
clearTimeout ( this . dblTimeout ) ;
// Konva.inDblClickWindow = false;
2019-03-07 11:19:32 +08:00
} else if ( ! DD . justDragged ) {
2019-01-02 04:59:27 +08:00
// don't set inDblClickWindow after dragging
2019-03-07 11:19:32 +08:00
Konva . inDblClickWindow = true ;
2019-01-02 04:59:27 +08:00
clearTimeout ( this . dblTimeout ) ;
}
this . dblTimeout = setTimeout ( function ( ) {
2019-03-07 11:19:32 +08:00
Konva . inDblClickWindow = false ;
} , Konva . dblClickWindow ) ;
2019-01-02 04:59:27 +08:00
if ( shape && shape . isListening ( ) ) {
this . clickEndShape = shape ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( MOUSEUP , { evt : evt , pointerId } ) ;
2019-01-02 04:59:27 +08:00
// detect if click or double click occurred
if (
2019-03-07 11:19:32 +08:00
Konva . listenClickTap &&
2019-01-02 04:59:27 +08:00
clickStartShape &&
clickStartShape . _id === shape . _id
) {
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( CLICK , { evt : evt , pointerId } ) ;
2019-01-02 04:59:27 +08:00
2019-07-18 08:55:22 +08:00
if ( fireDblClick && clickEndShape && clickEndShape === shape ) {
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( DBL_CLICK , { evt : evt , pointerId } ) ;
2019-01-02 04:59:27 +08:00
}
}
} else {
2019-08-04 15:38:57 +08:00
this . _fire ( MOUSEUP , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId
} ) ;
2019-03-07 11:19:32 +08:00
if ( Konva . listenClickTap ) {
2019-08-04 15:38:57 +08:00
this . _fire ( CLICK , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId
} ) ;
2019-01-02 04:59:27 +08:00
}
if ( fireDblClick ) {
this . _fire ( DBL_CLICK , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId
2019-01-02 04:59:27 +08:00
} ) ;
}
}
// content events
this . _fire ( CONTENT_MOUSEUP , { evt : evt } ) ;
2019-03-07 11:19:32 +08:00
if ( Konva . listenClickTap ) {
2019-01-02 04:59:27 +08:00
this . _fire ( CONTENT_CLICK , { evt : evt } ) ;
if ( fireDblClick ) {
this . _fire ( CONTENT_DBL_CLICK , { evt : evt } ) ;
}
}
2019-03-07 11:19:32 +08:00
Konva . listenClickTap = false ;
2019-01-02 04:59:27 +08:00
// always call preventDefault for desktop events because some browsers
// try to drag and drop the canvas element
if ( evt . cancelable ) {
evt . preventDefault ( ) ;
}
}
_contextmenu ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
var shape = this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape && shape . isListening ( ) ) {
shape . _fireAndBubble ( CONTEXTMENU , { evt : evt } ) ;
} else {
this . _fire ( CONTEXTMENU , {
evt : evt ,
target : this ,
currentTarget : this
} ) ;
}
this . _fire ( CONTENT_CONTEXTMENU , { evt : evt } ) ;
}
_touchstart ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-08-04 10:41:57 +08:00
var triggeredOnShape = false ;
this . _changedPointerPositions . forEach ( pos = > {
var shape = this . getIntersection ( pos ) ;
Konva . listenClickTap = true ;
2019-11-13 04:10:36 +08:00
DD . justDragged = false ;
2019-08-04 10:41:57 +08:00
const hasShape = shape && shape . isListening ( ) ;
2019-01-02 04:59:27 +08:00
2019-08-04 10:41:57 +08:00
if ( ! hasShape ) {
return ;
}
2019-01-02 04:59:27 +08:00
2019-08-04 10:41:57 +08:00
if ( Konva . captureTouchEventsEnabled ) {
shape . setPointerCapture ( pos . id ) ;
}
2019-01-02 04:59:27 +08:00
2019-08-04 10:41:57 +08:00
this . tapStartShape = shape ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( TOUCHSTART , { evt : evt , pointerId : pos.id } , this ) ;
2019-08-04 10:41:57 +08:00
triggeredOnShape = true ;
2019-01-02 04:59:27 +08:00
// only call preventDefault if the shape is listening for events
if ( shape . isListening ( ) && shape . preventDefault ( ) && evt . cancelable ) {
evt . preventDefault ( ) ;
}
2019-08-04 10:41:57 +08:00
} ) ;
if ( ! triggeredOnShape ) {
2019-01-02 04:59:27 +08:00
this . _fire ( TOUCHSTART , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId : this._changedPointerPositions [ 0 ] . id
2019-01-02 04:59:27 +08:00
} ) ;
}
2019-08-04 10:41:57 +08:00
2019-01-02 04:59:27 +08:00
// content event
this . _fire ( CONTENT_TOUCHSTART , { evt : evt } ) ;
}
2019-08-04 10:41:57 +08:00
_touchmove ( evt ) {
this . setPointersPositions ( evt ) ;
2019-08-08 17:24:55 +08:00
var eventsEnabled = ! DD . isDragging || Konva . hitOnDragEnabled ;
if ( eventsEnabled ) {
2019-08-04 10:41:57 +08:00
var triggeredOnShape = false ;
var processedShapesIds = { } ;
this . _changedPointerPositions . forEach ( pos = > {
const shape =
PointerEvents . getCapturedShape ( pos . id ) || this . getIntersection ( pos ) ;
const hasShape = shape && shape . isListening ( ) ;
if ( ! hasShape ) {
return ;
}
if ( processedShapesIds [ shape . _id ] ) {
return ;
}
processedShapesIds [ shape . _id ] = true ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( TOUCHMOVE , { evt : evt , pointerId : pos.id } ) ;
2019-08-04 10:41:57 +08:00
triggeredOnShape = true ;
// only call preventDefault if the shape is listening for events
if ( shape . isListening ( ) && shape . preventDefault ( ) && evt . cancelable ) {
evt . preventDefault ( ) ;
}
} ) ;
if ( ! triggeredOnShape ) {
this . _fire ( TOUCHMOVE , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId : this._changedPointerPositions [ 0 ] . id
2019-08-04 10:41:57 +08:00
} ) ;
}
this . _fire ( CONTENT_TOUCHMOVE , { evt : evt } ) ;
}
if ( DD . isDragging && DD . node . preventDefault ( ) && evt . cancelable ) {
evt . preventDefault ( ) ;
}
}
2019-01-02 04:59:27 +08:00
_touchend ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-08-04 10:41:57 +08:00
var clickEndShape = this . clickEndShape ,
2019-01-02 04:59:27 +08:00
fireDblClick = false ;
2019-03-07 11:19:32 +08:00
if ( Konva . inDblClickWindow ) {
2019-01-02 04:59:27 +08:00
fireDblClick = true ;
clearTimeout ( this . dblTimeout ) ;
2019-03-07 11:19:32 +08:00
// Konva.inDblClickWindow = false;
2019-09-03 22:38:19 +08:00
} else if ( ! DD . justDragged ) {
2019-03-07 11:19:32 +08:00
Konva . inDblClickWindow = true ;
2019-01-02 04:59:27 +08:00
clearTimeout ( this . dblTimeout ) ;
}
this . dblTimeout = setTimeout ( function ( ) {
2019-03-07 11:19:32 +08:00
Konva . inDblClickWindow = false ;
} , Konva . dblClickWindow ) ;
2019-01-02 04:59:27 +08:00
2019-08-04 10:41:57 +08:00
var triggeredOnShape = false ;
var processedShapesIds = { } ;
2019-08-17 13:47:48 +08:00
var tapTriggered = false ;
var dblTapTriggered = false ;
2019-08-04 10:41:57 +08:00
this . _changedPointerPositions . forEach ( pos = > {
var shape =
( PointerEvents . getCapturedShape ( pos . id ) as Shape ) ||
this . getIntersection ( pos ) ;
if ( shape ) {
shape . releaseCapture ( pos . id ) ;
}
const hasShape = shape && shape . isListening ( ) ;
if ( ! hasShape ) {
return ;
}
if ( processedShapesIds [ shape . _id ] ) {
return ;
}
processedShapesIds [ shape . _id ] = true ;
2019-07-18 08:55:22 +08:00
this . clickEndShape = shape ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( TOUCHEND , { evt : evt , pointerId : pos.id } ) ;
2019-08-04 10:41:57 +08:00
triggeredOnShape = true ;
2019-01-02 04:59:27 +08:00
// detect if tap or double tap occurred
2019-08-17 13:47:48 +08:00
if ( Konva . listenClickTap && shape === this . tapStartShape ) {
tapTriggered = true ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( TAP , { evt : evt , pointerId : pos.id } ) ;
2019-01-02 04:59:27 +08:00
2019-07-18 08:55:22 +08:00
if ( fireDblClick && clickEndShape && clickEndShape === shape ) {
2019-08-17 13:47:48 +08:00
dblTapTriggered = true ;
2019-08-04 15:38:57 +08:00
shape . _fireAndBubble ( DBL_TAP , { evt : evt , pointerId : pos.id } ) ;
2019-01-02 04:59:27 +08:00
}
}
2019-08-04 10:41:57 +08:00
2019-01-02 04:59:27 +08:00
// only call preventDefault if the shape is listening for events
if ( shape . isListening ( ) && shape . preventDefault ( ) && evt . cancelable ) {
evt . preventDefault ( ) ;
}
2019-08-04 10:41:57 +08:00
} ) ;
if ( ! triggeredOnShape ) {
2019-08-04 15:38:57 +08:00
this . _fire ( TOUCHEND , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId : this._changedPointerPositions [ 0 ] . id
} ) ;
2019-08-04 10:41:57 +08:00
}
2019-08-17 13:47:48 +08:00
if ( Konva . listenClickTap && ! tapTriggered ) {
2019-08-04 15:38:57 +08:00
this . _fire ( TAP , {
evt : evt ,
target : this ,
currentTarget : this ,
pointerId : this._changedPointerPositions [ 0 ] . id
} ) ;
2019-08-04 10:41:57 +08:00
}
2019-08-17 13:47:48 +08:00
if ( fireDblClick && ! dblTapTriggered ) {
2019-08-04 10:41:57 +08:00
this . _fire ( DBL_TAP , {
evt : evt ,
target : this ,
2019-08-04 15:38:57 +08:00
currentTarget : this ,
pointerId : this._changedPointerPositions [ 0 ] . id
2019-08-04 10:41:57 +08:00
} ) ;
2019-01-02 04:59:27 +08:00
}
// content events
this . _fire ( CONTENT_TOUCHEND , { evt : evt } ) ;
2019-03-07 11:19:32 +08:00
if ( Konva . listenClickTap ) {
2019-01-02 04:59:27 +08:00
this . _fire ( CONTENT_TAP , { evt : evt } ) ;
if ( fireDblClick ) {
this . _fire ( CONTENT_DBL_TAP , { evt : evt } ) ;
}
}
2019-03-07 11:19:32 +08:00
Konva . listenClickTap = false ;
2019-01-02 04:59:27 +08:00
}
2019-08-04 10:41:57 +08:00
2019-01-02 04:59:27 +08:00
_wheel ( evt ) {
2019-02-20 09:17:49 +08:00
this . setPointersPositions ( evt ) ;
2019-01-02 04:59:27 +08:00
var shape = this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape && shape . isListening ( ) ) {
shape . _fireAndBubble ( WHEEL , { evt : evt } ) ;
} else {
this . _fire ( WHEEL , {
evt : evt ,
target : this ,
currentTarget : this
} ) ;
}
this . _fire ( CONTENT_WHEEL , { evt : evt } ) ;
}
2019-03-27 04:30:00 +08:00
_pointerdown ( evt : PointerEvent ) {
2019-05-28 03:39:17 +08:00
if ( ! Konva . _pointerEventsEnabled ) {
return ;
}
2019-03-27 04:30:00 +08:00
this . setPointersPositions ( evt ) ;
const shape =
PointerEvents . getCapturedShape ( evt . pointerId ) ||
this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape ) {
shape . _fireAndBubble ( POINTERDOWN , PointerEvents . createEvent ( evt ) ) ;
}
}
_pointermove ( evt : PointerEvent ) {
2019-05-28 03:39:17 +08:00
if ( ! Konva . _pointerEventsEnabled ) {
return ;
}
2019-03-27 04:30:00 +08:00
this . setPointersPositions ( evt ) ;
const shape =
PointerEvents . getCapturedShape ( evt . pointerId ) ||
this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape ) {
shape . _fireAndBubble ( POINTERMOVE , PointerEvents . createEvent ( evt ) ) ;
}
}
_pointerup ( evt : PointerEvent ) {
2019-05-28 03:39:17 +08:00
if ( ! Konva . _pointerEventsEnabled ) {
return ;
}
2019-03-27 04:30:00 +08:00
this . setPointersPositions ( evt ) ;
const shape =
PointerEvents . getCapturedShape ( evt . pointerId ) ||
this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape ) {
shape . _fireAndBubble ( POINTERUP , PointerEvents . createEvent ( evt ) ) ;
}
PointerEvents . releaseCapture ( evt . pointerId ) ;
}
_pointercancel ( evt : PointerEvent ) {
2019-05-28 03:39:17 +08:00
if ( ! Konva . _pointerEventsEnabled ) {
return ;
}
2019-03-27 04:30:00 +08:00
this . setPointersPositions ( evt ) ;
const shape =
PointerEvents . getCapturedShape ( evt . pointerId ) ||
this . getIntersection ( this . getPointerPosition ( ) ) ;
if ( shape ) {
shape . _fireAndBubble ( POINTERUP , PointerEvents . createEvent ( evt ) ) ;
}
PointerEvents . releaseCapture ( evt . pointerId ) ;
}
_lostpointercapture ( evt : PointerEvent ) {
PointerEvents . releaseCapture ( evt . pointerId ) ;
}
2019-02-20 09:17:49 +08:00
/ * *
* manually register pointers positions ( mouse / touch ) in the stage .
* So you can use stage . getPointerPosition ( ) . Usually you don ' t need to use that method
* because all internal events are automatically registered . It may be useful if event
* is triggered outside of the stage , but you still want to use Konva methods to get pointers position .
* @method
* @name Konva . Stage # setPointersPositions
* @param { Object } event Event object
* @example
*
* window . addEventListener ( 'mousemove' , ( e ) = > {
* stage . setPointersPositions ( e ) ;
* } ) ;
* /
setPointersPositions ( evt ) {
2019-01-02 04:59:27 +08:00
var contentPosition = this . _getContentPosition ( ) ,
x = null ,
y = null ;
evt = evt ? evt : window.event ;
// touch events
if ( evt . touches !== undefined ) {
2019-08-04 10:41:57 +08:00
// touchlist has not support for map method
// so we have to iterate
this . _pointerPositions = [ ] ;
this . _changedPointerPositions = [ ] ;
Collection . prototype . each . call ( evt . touches , ( touch : any ) = > {
this . _pointerPositions . push ( {
id : touch.identifier ,
x : touch.clientX - contentPosition . left ,
y : touch.clientY - contentPosition . top
} ) ;
} ) ;
Collection . prototype . each . call (
evt . changedTouches || evt . touches ,
( touch : any ) = > {
this . _changedPointerPositions . push ( {
id : touch.identifier ,
x : touch.clientX - contentPosition . left ,
y : touch.clientY - contentPosition . top
} ) ;
}
) ;
2019-01-02 04:59:27 +08:00
// 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 ;
this . pointerPos = {
x : x ,
y : y
} ;
2019-08-04 15:38:57 +08:00
this . _pointerPositions = [ { x , y , id : Util._getFirstPointerId ( evt ) } ] ;
this . _changedPointerPositions = [
{ x , y , id : Util._getFirstPointerId ( evt ) }
] ;
2019-01-02 04:59:27 +08:00
}
}
2019-02-20 09:17:49 +08:00
_setPointerPosition ( evt ) {
Util . warn (
'Method _setPointerPosition is deprecated. Use "stage.setPointersPositions(event)" instead.'
) ;
this . setPointersPositions ( evt ) ;
}
2019-01-02 04:59:27 +08:00
_getContentPosition() {
var rect = this . content . getBoundingClientRect
? this . content . getBoundingClientRect ( )
: { top : 0 , left : 0 } ;
return {
top : rect.top ,
left : rect.left
} ;
}
_buildDOM() {
// 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 SceneCanvas ( ) ;
this . bufferHitCanvas = new HitCanvas ( { pixelRatio : 1 } ) ;
2019-03-07 11:19:32 +08:00
if ( ! Konva . isBrowser ) {
2019-01-02 04:59:27 +08:00
return ;
}
var container = this . container ( ) ;
if ( ! container ) {
throw 'Stage has no container. A container is required.' ;
}
// clear content inside container
container . innerHTML = EMPTY_STRING ;
// content
2019-03-07 11:19:32 +08:00
this . content = document . createElement ( 'div' ) ;
2019-01-02 04:59:27 +08:00
this . content . style . position = RELATIVE ;
this . content . style . userSelect = 'none' ;
this . content . className = KONVA_CONTENT ;
this . content . setAttribute ( 'role' , 'presentation' ) ;
container . appendChild ( this . content ) ;
this . _resizeDOM ( ) ;
}
// currently cache function is now working for stage, because stage has no its own canvas element
cache() {
Util . warn (
'Cache function is not allowed for stage. You may use cache only for layers, groups and shapes.'
) ;
return this ;
}
clearCache() {
return this ;
}
2019-01-25 13:20:15 +08:00
/ * *
* batch draw
* @method
* @name Konva . BaseLayer # batchDraw
* @return { Konva . Stage } this
* /
batchDraw() {
this . children . each ( function ( layer ) {
layer . batchDraw ( ) ;
} ) ;
return this ;
}
2019-01-02 04:59:27 +08:00
container : GetSet < HTMLDivElement , this > ;
}
2019-01-28 04:43:50 +08:00
Stage . prototype . nodeType = STAGE ;
2019-02-27 21:06:04 +08:00
_registerNode ( Stage ) ;
2019-01-28 04:43:50 +08:00
2019-01-02 04:59:27 +08:00
/ * *
2019-01-06 16:01:20 +08:00
* get / set container DOM element
2019-01-02 04:59:27 +08:00
* @method
2019-01-06 16:01:20 +08:00
* @name Konva . Stage # container
2019-01-02 04:59:27 +08:00
* @returns { DomElement } container
* @example
* // get container
* var container = stage . container ( ) ;
* // set container
* var container = document . createElement ( 'div' ) ;
* body . appendChild ( container ) ;
* stage . container ( container ) ;
* /
2019-02-19 01:12:03 +08:00
Factory . addGetterSetter ( Stage , 'container' ) ;