konva/src/Layer.ts

262 lines
6.7 KiB
TypeScript
Raw Normal View History

2020-04-07 21:46:50 +08:00
import { Util, Collection } from './Util';
2019-01-02 04:59:27 +08:00
import { Container } from './Container';
2019-02-25 01:06:04 +08:00
import { Factory } from './Factory';
2019-01-02 04:59:27 +08:00
import { BaseLayer } from './BaseLayer';
2019-01-06 16:01:20 +08:00
import { HitCanvas } from './Canvas';
2019-02-24 09:54:20 +08:00
import { shapes } from './Shape';
2019-02-25 01:06:04 +08:00
import { getBooleanValidator } from './Validators';
2019-02-27 21:06:04 +08:00
import { _registerNode } from './Global';
2019-01-02 04:59:27 +08:00
2020-04-07 21:46:50 +08:00
import { GetSet, Vector2d } from './types';
2019-01-02 04:59:27 +08:00
// constants
var HASH = '#',
BEFORE_DRAW = 'beforeDraw',
DRAW = 'draw',
/*
* 2 - 3 - 4
* | |
* 1 - 0 5
* |
* 8 - 7 - 6
*/
INTERSECTION_OFFSETS = [
{ x: 0, y: 0 }, // 0
{ x: -1, y: -1 }, // 2
{ x: 1, y: -1 }, // 4
{ x: 1, y: 1 }, // 6
{ x: -1, y: 1 } // 8
],
INTERSECTION_OFFSETS_LEN = INTERSECTION_OFFSETS.length;
/**
* Layer constructor. Layers are tied to their own canvas element and are used
* to contain groups or shapes.
* @constructor
* @memberof Konva
* @augments Konva.BaseLayer
* @param {Object} config
* @param {Boolean} [config.clearBeforeDraw] set this property to false if you don't want
* to clear the canvas before each layer draw. The default value is true.
* @@nodeParams
* @@containerParams
* @example
* var layer = new Konva.Layer();
* stage.add(layer);
* // now you can add shapes, groups into the layer
*/
export class Layer extends BaseLayer {
2019-01-25 13:20:15 +08:00
hitCanvas = new HitCanvas({
pixelRatio: 1
});
2019-01-02 04:59:27 +08:00
_setCanvasSize(width, height) {
this.canvas.setSize(width, height);
this.hitCanvas.setSize(width, height);
this._checkSmooth();
2019-01-02 04:59:27 +08:00
}
_validateAdd(child) {
var type = child.getType();
if (type !== 'Group' && type !== 'Shape') {
Util.throw('You may only add groups and shapes to a layer.');
}
}
/**
* get visible intersection shape. This is the preferred
* method for determining if a point intersects a shape or not
2019-02-20 22:13:39 +08:00
* also you may pass optional selector parameter to return ancestor of intersected shape
2019-01-02 04:59:27 +08:00
* @method
2019-02-20 22:13:39 +08:00
* @name Konva.Layer#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 = layer.getIntersection({x: 50, y: 50});
* // or if you interested in shape parent:
* var group = layer.getIntersection({x: 50, y: 50}, 'Group');
*/
2020-04-07 21:46:50 +08:00
getIntersection(pos: Vector2d, selector?: string) {
2019-01-02 04:59:27 +08:00
var obj, i, intersectionOffset, shape;
if (!this.hitGraphEnabled() || !this.isVisible()) {
return null;
}
// in some cases antialiased area may be bigger than 1px
// it is possible if we will cache node, then scale it a lot
var spiralSearchDistance = 1;
var continueSearch = false;
while (true) {
for (i = 0; i < INTERSECTION_OFFSETS_LEN; i++) {
intersectionOffset = INTERSECTION_OFFSETS[i];
obj = this._getIntersection({
x: pos.x + intersectionOffset.x * spiralSearchDistance,
y: pos.y + intersectionOffset.y * spiralSearchDistance
});
shape = obj.shape;
if (shape && selector) {
return shape.findAncestor(selector, true);
} else if (shape) {
return shape;
}
// we should continue search if we found antialiased pixel
// that means our node somewhere very close
continueSearch = !!obj.antialiased;
// stop search if found empty pixel
if (!obj.antialiased) {
break;
}
}
// if no shape, and no antialiased pixel, we should end searching
if (continueSearch) {
spiralSearchDistance += 1;
} else {
return null;
}
}
}
_getIntersection(pos) {
var ratio = this.hitCanvas.pixelRatio;
var p = this.hitCanvas.context.getImageData(
Math.round(pos.x * ratio),
Math.round(pos.y * ratio),
1,
1
).data,
p3 = p[3],
colorKey,
shape;
// fully opaque pixel
if (p3 === 255) {
colorKey = Util._rgbToHex(p[0], p[1], p[2]);
shape = shapes[HASH + colorKey];
if (shape) {
return {
shape: shape
};
}
return {
antialiased: true
};
} else if (p3 > 0) {
// antialiased pixel
return {
antialiased: true
};
}
// empty pixel
return {};
}
drawScene(can, top) {
var layer = this.getLayer(),
canvas = can || (layer && layer.getCanvas());
this._fire(BEFORE_DRAW, {
node: this
});
if (this.clearBeforeDraw()) {
canvas.getContext().clear();
}
Container.prototype.drawScene.call(this, canvas, top);
this._fire(DRAW, {
node: this
});
return this;
}
drawHit(can, top) {
var layer = this.getLayer(),
canvas = can || (layer && layer.hitCanvas);
if (layer && layer.clearBeforeDraw()) {
layer
.getHitCanvas()
.getContext()
.clear();
}
Container.prototype.drawHit.call(this, canvas, top);
return this;
}
2019-03-22 06:59:53 +08:00
clear(bounds?) {
2019-01-02 04:59:27 +08:00
BaseLayer.prototype.clear.call(this, bounds);
this.getHitCanvas()
.getContext()
.clear(bounds);
return this;
}
/**
* enable hit graph
2019-01-06 16:01:20 +08:00
* @name Konva.Layer#enableHitGraph
2019-01-02 04:59:27 +08:00
* @method
* @returns {Layer}
*/
enableHitGraph() {
this.hitGraphEnabled(true);
return this;
}
/**
* disable hit graph
2019-01-06 16:01:20 +08:00
* @name Konva.Layer#disableHitGraph
2019-01-02 04:59:27 +08:00
* @method
* @returns {Layer}
*/
disableHitGraph() {
this.hitGraphEnabled(false);
return this;
}
2019-02-19 01:12:03 +08:00
2019-02-20 22:13:39 +08:00
/**
* Show or hide hit canvas over the stage. May be useful for debugging custom hitFunc
* @name Konva.Layer#toggleHitCanvas
* @method
*/
2019-02-19 01:12:03 +08:00
toggleHitCanvas() {
if (!this.parent) {
return;
}
var parent = this.parent as any;
var added = !!this.hitCanvas._canvas.parentNode;
if (added) {
parent.content.removeChild(this.hitCanvas._canvas);
} else {
parent.content.appendChild(this.hitCanvas._canvas);
}
}
2019-01-02 04:59:27 +08:00
setSize({ width, height }) {
super.setSize({ width, height });
this.hitCanvas.setSize(width, height);
return this;
}
hitGraphEnabled: GetSet<boolean, this>;
}
2019-02-27 21:06:04 +08:00
Layer.prototype.nodeType = 'Layer';
_registerNode(Layer);
2019-02-25 01:06:04 +08:00
Factory.addGetterSetter(Layer, 'hitGraphEnabled', true, getBooleanValidator());
2019-01-02 04:59:27 +08:00
/**
* get/set hitGraphEnabled flag. Disabling the hit graph will greatly increase
* draw performance because the hit graph will not be redrawn each time the layer is
* drawn. This, however, also disables mouse/touch event detection
2019-01-06 16:01:20 +08:00
* @name Konva.Layer#hitGraphEnabled
2019-01-02 04:59:27 +08:00
* @method
* @param {Boolean} enabled
* @returns {Boolean}
* @example
* // get hitGraphEnabled flag
* var hitGraphEnabled = layer.hitGraphEnabled();
*
* // disable hit graph
* layer.hitGraphEnabled(false);
*
* // enable hit graph
* layer.hitGraphEnabled(true);
*/
Collection.mapMethods(Layer);