You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
657 lines
27 KiB
657 lines
27 KiB
/*!
|
|
* @antv/g-plugin-canvas-picker
|
|
* @description A G plugin for picking in canvas
|
|
* @version 2.1.27
|
|
* @date 7/30/2025, 1:37:32 PM
|
|
* @author AntVis
|
|
* @docs https://g.antv.antgroup.com/
|
|
*/
|
|
'use strict';
|
|
|
|
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
|
|
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
|
|
var _createClass = require('@babel/runtime/helpers/createClass');
|
|
var _callSuper = require('@babel/runtime/helpers/callSuper');
|
|
var _inherits = require('@babel/runtime/helpers/inherits');
|
|
var gLite = require('@antv/g-lite');
|
|
var _createForOfIteratorHelper = require('@babel/runtime/helpers/createForOfIteratorHelper');
|
|
var _regeneratorRuntime = require('@babel/runtime/helpers/regeneratorRuntime');
|
|
var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator');
|
|
var glMatrix = require('gl-matrix');
|
|
var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
|
|
var gMath = require('@antv/g-math');
|
|
var util = require('@antv/util');
|
|
var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
|
|
|
|
var tmpVec3a = glMatrix.vec3.create();
|
|
var tmpVec3b = glMatrix.vec3.create();
|
|
var tmpVec3c = glMatrix.vec3.create();
|
|
var tmpMat4 = glMatrix.mat4.create();
|
|
/**
|
|
* pick shape(s) with Mouse/Touch event
|
|
*
|
|
* 1. find AABB with r-tree
|
|
* 2. do math calculation with geometry in an accurate way
|
|
*/
|
|
var CanvasPickerPlugin = /*#__PURE__*/function () {
|
|
function CanvasPickerPlugin() {
|
|
var _this = this;
|
|
_classCallCheck(this, CanvasPickerPlugin);
|
|
this.isHit = function (displayObject, position, worldTransform, isClipPath) {
|
|
// use picker for current shape's type
|
|
var pick = _this.context.pointInPathPickerFactory[displayObject.nodeName];
|
|
if (pick) {
|
|
// invert with world matrix
|
|
var invertWorldMat = glMatrix.mat4.invert(tmpMat4, worldTransform);
|
|
|
|
// transform client position to local space, do picking in local space
|
|
var localPosition = glMatrix.vec3.transformMat4(tmpVec3b, glMatrix.vec3.set(tmpVec3c, position[0], position[1], 0), invertWorldMat);
|
|
if (pick(displayObject, new gLite.Point(localPosition[0], localPosition[1]), isClipPath, _this.isPointInPath, _this.context, _this.runtime)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
/**
|
|
* use native picking method
|
|
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/isPointInPath
|
|
*/
|
|
this.isPointInPath = function (displayObject, position) {
|
|
var context = _this.runtime.offscreenCanvasCreator.getOrCreateContext(_this.context.config.offscreenCanvas);
|
|
var generatePath = _this.context.pathGeneratorFactory[displayObject.nodeName];
|
|
if (generatePath) {
|
|
context.beginPath();
|
|
generatePath(context, displayObject.parsedStyle);
|
|
context.closePath();
|
|
}
|
|
return context.isPointInPath(position.x, position.y);
|
|
};
|
|
}
|
|
return _createClass(CanvasPickerPlugin, [{
|
|
key: "apply",
|
|
value: function apply(context, runtime) {
|
|
var _renderingContext$roo,
|
|
_this2 = this;
|
|
var renderingService = context.renderingService,
|
|
renderingContext = context.renderingContext;
|
|
this.context = context;
|
|
this.runtime = runtime;
|
|
var document = (_renderingContext$roo = renderingContext.root) === null || _renderingContext$roo === void 0 ? void 0 : _renderingContext$roo.ownerDocument;
|
|
renderingService.hooks.pick.tapPromise(CanvasPickerPlugin.tag, /*#__PURE__*/function () {
|
|
var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(result) {
|
|
return _regeneratorRuntime().wrap(function (_context) {
|
|
while (1) switch (_context.prev = _context.next) {
|
|
case 0:
|
|
return _context.abrupt("return", _this2.pick(document, result));
|
|
case 1:
|
|
case "end":
|
|
return _context.stop();
|
|
}
|
|
}, _callee);
|
|
}));
|
|
return function (_x) {
|
|
return _ref.apply(this, arguments);
|
|
};
|
|
}());
|
|
renderingService.hooks.pickSync.tap(CanvasPickerPlugin.tag, function (result) {
|
|
return _this2.pick(document, result);
|
|
});
|
|
}
|
|
}, {
|
|
key: "pick",
|
|
value: function pick(document, result) {
|
|
var topmost = result.topmost,
|
|
_result$position = result.position,
|
|
x = _result$position.x,
|
|
y = _result$position.y;
|
|
|
|
// position in world space
|
|
var position = glMatrix.vec3.set(tmpVec3a, x, y, 0);
|
|
|
|
// query by AABB first with spatial index(r-tree)
|
|
var hitTestList = document.elementsFromBBox(position[0], position[1], position[0], position[1]);
|
|
|
|
// test with clip path & origin shape
|
|
// @see https://github.com/antvis/g/issues/1064
|
|
var pickedDisplayObjects = [];
|
|
var _iterator = _createForOfIteratorHelper(hitTestList),
|
|
_step;
|
|
try {
|
|
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
var _displayObject = _step.value;
|
|
var worldTransform = _displayObject.getWorldTransform();
|
|
var isHitOriginShape = this.isHit(_displayObject, position, worldTransform, false);
|
|
if (isHitOriginShape) {
|
|
// should look up in the ancestor node
|
|
var clipped = gLite.findClosestClipPathTarget(_displayObject);
|
|
if (clipped) {
|
|
var clipPath = clipped.parsedStyle.clipPath;
|
|
var isHitClipPath = this.isHit(clipPath, position, clipPath.getWorldTransform(), true);
|
|
if (isHitClipPath) {
|
|
if (topmost) {
|
|
result.picked = [_displayObject];
|
|
return result;
|
|
}
|
|
pickedDisplayObjects.push(_displayObject);
|
|
}
|
|
} else {
|
|
if (topmost) {
|
|
result.picked = [_displayObject];
|
|
return result;
|
|
}
|
|
pickedDisplayObjects.push(_displayObject);
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
_iterator.e(err);
|
|
} finally {
|
|
_iterator.f();
|
|
}
|
|
result.picked = pickedDisplayObjects;
|
|
return result;
|
|
}
|
|
}]);
|
|
}();
|
|
CanvasPickerPlugin.tag = 'CanvasPicker';
|
|
|
|
function isPointInPath$8(displayObject, position, isClipPath) {
|
|
var _ref = displayObject.parsedStyle,
|
|
_ref$cx = _ref.cx,
|
|
cx = _ref$cx === void 0 ? 0 : _ref$cx,
|
|
_ref$cy = _ref.cy,
|
|
cy = _ref$cy === void 0 ? 0 : _ref$cy,
|
|
r = _ref.r,
|
|
fill = _ref.fill,
|
|
stroke = _ref.stroke,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents;
|
|
var halfLineWidth = (lineWidth + increasedLineWidthForHitTesting) / 2;
|
|
var absDistance = gMath.distance(cx, cy, position.x, position.y);
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents, fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasFill = _isFillOrStrokeAffect2[0],
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
if (hasFill && hasStroke || isClipPath) {
|
|
return absDistance <= r + halfLineWidth;
|
|
}
|
|
if (hasFill) {
|
|
return absDistance <= r;
|
|
}
|
|
if (hasStroke) {
|
|
return absDistance >= r - halfLineWidth && absDistance <= r + halfLineWidth;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function ellipseDistance(squareX, squareY, rx, ry) {
|
|
return squareX / (rx * rx) + squareY / (ry * ry);
|
|
}
|
|
function isPointInPath$7(displayObject, position, isClipPath) {
|
|
var _ref = displayObject.parsedStyle,
|
|
_ref$cx = _ref.cx,
|
|
cx = _ref$cx === void 0 ? 0 : _ref$cx,
|
|
_ref$cy = _ref.cy,
|
|
cy = _ref$cy === void 0 ? 0 : _ref$cy,
|
|
rx = _ref.rx,
|
|
ry = _ref.ry,
|
|
fill = _ref.fill,
|
|
stroke = _ref.stroke,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents;
|
|
var x = position.x,
|
|
y = position.y;
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents, fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasFill = _isFillOrStrokeAffect2[0],
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
var halfLineWith = (lineWidth + increasedLineWidthForHitTesting) / 2;
|
|
var squareX = (x - cx) * (x - cx);
|
|
var squareY = (y - cy) * (y - cy);
|
|
// 使用椭圆的公式: x*x/rx*rx + y*y/ry*ry = 1;
|
|
if (hasFill && hasStroke || isClipPath) {
|
|
return ellipseDistance(squareX, squareY, rx + halfLineWith, ry + halfLineWith) <= 1;
|
|
}
|
|
if (hasFill) {
|
|
return ellipseDistance(squareX, squareY, rx, ry) <= 1;
|
|
}
|
|
if (hasStroke) {
|
|
return ellipseDistance(squareX, squareY, rx - halfLineWith, ry - halfLineWith) >= 1 && ellipseDistance(squareX, squareY, rx + halfLineWith, ry + halfLineWith) <= 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function inBox(minX, minY, width, height, x, y) {
|
|
return x >= minX && x <= minX + width && y >= minY && y <= minY + height;
|
|
}
|
|
function inRect(minX, minY, width, height, lineWidth, x, y) {
|
|
var halfWidth = lineWidth / 2;
|
|
// 将四个边看做矩形来检测,比边的检测算法要快
|
|
return inBox(minX - halfWidth, minY - halfWidth, width, lineWidth, x, y) ||
|
|
// 上边
|
|
inBox(minX + width - halfWidth, minY - halfWidth, lineWidth, height, x, y) ||
|
|
// 右边
|
|
inBox(minX + halfWidth, minY + height - halfWidth, width, lineWidth, x, y) ||
|
|
// 下边
|
|
inBox(minX - halfWidth, minY + halfWidth, lineWidth, height, x, y); // 左边
|
|
}
|
|
function inArc(cx, cy, r, startAngle, endAngle, lineWidth, x, y) {
|
|
var angle = (Math.atan2(y - cy, x - cx) + Math.PI * 2) % (Math.PI * 2); // 转换到 0 - 2 * Math.PI 之间
|
|
// if (angle < startAngle || angle > endAngle) {
|
|
// return false;
|
|
// }
|
|
var point = {
|
|
x: cx + r * Math.cos(angle),
|
|
y: cy + r * Math.sin(angle)
|
|
};
|
|
return gMath.distance(point.x, point.y, x, y) <= lineWidth / 2;
|
|
}
|
|
function inLine(x1, y1, x2, y2, lineWidth, x, y) {
|
|
var minX = Math.min(x1, x2);
|
|
var maxX = Math.max(x1, x2);
|
|
var minY = Math.min(y1, y2);
|
|
var maxY = Math.max(y1, y2);
|
|
var halfWidth = lineWidth / 2;
|
|
// 因为目前的方案是计算点到直线的距离,而有可能会在延长线上,所以要先判断是否在包围盒内
|
|
// 这种方案会在水平或者竖直的情况下载线的延长线上有半 lineWidth 的误差
|
|
if (!(x >= minX - halfWidth && x <= maxX + halfWidth && y >= minY - halfWidth && y <= maxY + halfWidth)) {
|
|
return false;
|
|
}
|
|
// 因为已经计算了包围盒,所以仅需要计算到直线的距离即可,可以显著提升性能
|
|
return gMath.linePointToLine(x1, y1, x2, y2, x, y) <= lineWidth / 2;
|
|
}
|
|
function inPolyline(points, lineWidth, x, y, isClose) {
|
|
var count = points.length;
|
|
if (count < 2) {
|
|
return false;
|
|
}
|
|
for (var i = 0; i < count - 1; i++) {
|
|
var x1 = points[i][0];
|
|
var y1 = points[i][1];
|
|
var x2 = points[i + 1][0];
|
|
var y2 = points[i + 1][1];
|
|
if (inLine(x1, y1, x2, y2, lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 如果封闭,则计算起始点和结束点的边
|
|
if (isClose) {
|
|
var first = points[0];
|
|
var last = points[count - 1];
|
|
if (inLine(first[0], first[1], last[0], last[1], lineWidth, x, y)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 多边形的射线检测,参考:https://blog.csdn.net/WilliamSun0122/article/details/77994526
|
|
var tolerance = 1e-6;
|
|
// 三态函数,判断两个double在eps精度下的大小关系
|
|
function dcmp(x) {
|
|
if (Math.abs(x) < tolerance) {
|
|
return 0;
|
|
}
|
|
return x < 0 ? -1 : 1;
|
|
}
|
|
|
|
// 判断点Q是否在p1和p2的线段上
|
|
function onSegment(p1, p2, q) {
|
|
if ((q[0] - p1[0]) * (p2[1] - p1[1]) === (p2[0] - p1[0]) * (q[1] - p1[1]) && Math.min(p1[0], p2[0]) <= q[0] && q[0] <= Math.max(p1[0], p2[0]) && Math.min(p1[1], p2[1]) <= q[1] && q[1] <= Math.max(p1[1], p2[1])) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 判断点P在多边形内-射线法
|
|
function inPolygon(points, x, y) {
|
|
var isHit = false;
|
|
var n = points.length;
|
|
if (n <= 2) {
|
|
// svg 中点小于 3 个时,不显示,也无法被拾取
|
|
return false;
|
|
}
|
|
for (var i = 0; i < n; i++) {
|
|
var p1 = points[i];
|
|
var p2 = points[(i + 1) % n];
|
|
if (onSegment(p1, p2, [x, y])) {
|
|
// 点在多边形一条边上
|
|
return true;
|
|
}
|
|
// 前一个判断min(p1[1],p2[1])<P.y<=max(p1[1],p2[1])
|
|
// 后一个判断被测点 在 射线与边交点 的左边
|
|
if (dcmp(p1[1] - y) > 0 !== dcmp(p2[1] - y) > 0 && dcmp(x - (y - p1[1]) * (p1[0] - p2[0]) / (p1[1] - p2[1]) - p1[0]) < 0) {
|
|
isHit = !isHit;
|
|
}
|
|
}
|
|
return isHit;
|
|
}
|
|
function inPolygons(polygons, x, y) {
|
|
var isHit = false;
|
|
for (var i = 0; i < polygons.length; i++) {
|
|
var points = polygons[i];
|
|
isHit = inPolygon(points, x, y);
|
|
if (isHit) {
|
|
break;
|
|
}
|
|
}
|
|
return isHit;
|
|
}
|
|
|
|
function isPointInPath$6(displayObject, position, isClipPath) {
|
|
var _ref = displayObject.parsedStyle,
|
|
x1 = _ref.x1,
|
|
y1 = _ref.y1,
|
|
x2 = _ref.x2,
|
|
y2 = _ref.y2,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents,
|
|
fill = _ref.fill,
|
|
stroke = _ref.stroke;
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents, fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
if (!hasStroke && !isClipPath || !lineWidth) {
|
|
return false;
|
|
}
|
|
return inLine(x1, y1, x2, y2, lineWidth + increasedLineWidthForHitTesting, position.x, position.y);
|
|
}
|
|
|
|
// TODO: replace it with method in @antv/util
|
|
function isPointInStroke(segments, lineWidth, px, py, length) {
|
|
var isHit = false;
|
|
var halfWidth = lineWidth / 2;
|
|
for (var i = 0; i < segments.length; i++) {
|
|
var segment = segments[i];
|
|
var currentPoint = segment.currentPoint,
|
|
params = segment.params,
|
|
prePoint = segment.prePoint,
|
|
box = segment.box;
|
|
// 如果在前面已经生成过包围盒,直接按照包围盒计算
|
|
if (box && !inBox(box.x - halfWidth, box.y - halfWidth, box.width + lineWidth, box.height + lineWidth, px, py)) {
|
|
continue;
|
|
}
|
|
switch (segment.command) {
|
|
// L 和 Z 都是直线, M 不进行拾取
|
|
case 'L':
|
|
case 'Z':
|
|
isHit = inLine(prePoint[0], prePoint[1], currentPoint[0], currentPoint[1], lineWidth, px, py);
|
|
if (isHit) {
|
|
return true;
|
|
}
|
|
break;
|
|
case 'Q':
|
|
var qDistance = gMath.quadPointDistance(prePoint[0], prePoint[1], params[1], params[2], params[3], params[4], px, py);
|
|
isHit = qDistance <= lineWidth / 2;
|
|
if (isHit) {
|
|
return true;
|
|
}
|
|
break;
|
|
case 'C':
|
|
var cDistance = gMath.cubicPointDistance(prePoint[0],
|
|
// 上一段结束位置, 即 C 的起始点
|
|
prePoint[1], params[1],
|
|
// 'C' 的参数,1、2 为第一个控制点,3、4 为第二个控制点,5、6 为结束点
|
|
params[2], params[3], params[4], params[5], params[6], px, py, length);
|
|
isHit = cDistance <= lineWidth / 2;
|
|
if (isHit) {
|
|
return true;
|
|
}
|
|
break;
|
|
case 'A':
|
|
// cache conversion result
|
|
if (!segment.cubicParams) {
|
|
segment.cubicParams = util.arcToCubic(prePoint[0], prePoint[1], params[1], params[2], params[3], params[4], params[5], params[6], params[7], undefined);
|
|
}
|
|
var args = segment.cubicParams;
|
|
|
|
// fixArc
|
|
var prePointInCubic = prePoint;
|
|
for (var _i = 0; _i < args.length; _i += 6) {
|
|
var _cDistance = gMath.cubicPointDistance(prePointInCubic[0],
|
|
// 上一段结束位置, 即 C 的起始点
|
|
prePointInCubic[1], args[_i], args[_i + 1], args[_i + 2], args[_i + 3], args[_i + 4], args[_i + 5], px, py, length);
|
|
prePointInCubic = [args[_i + 4], args[_i + 5]];
|
|
isHit = _cDistance <= lineWidth / 2;
|
|
if (isHit) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return isHit;
|
|
}
|
|
function isPointInPath$5(displayObject, position, isClipPath, isPointInPath, renderingPluginContext, runtime) {
|
|
var _ref = displayObject.parsedStyle,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
stroke = _ref.stroke,
|
|
fill = _ref.fill,
|
|
d = _ref.d,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents;
|
|
var segments = d.segments,
|
|
hasArc = d.hasArc,
|
|
polylines = d.polylines,
|
|
polygons = d.polygons;
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents,
|
|
// Only a closed path can be filled.
|
|
(polygons === null || polygons === void 0 ? void 0 : polygons.length) && fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasFill = _isFillOrStrokeAffect2[0],
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
var totalLength = gLite.getOrCalculatePathTotalLength(displayObject);
|
|
var isHit = false;
|
|
if (hasFill || isClipPath) {
|
|
if (hasArc) {
|
|
// 存在曲线时,暂时使用 canvas 的 api 计算,后续可以进行多边形切割
|
|
isHit = isPointInPath(displayObject, position);
|
|
} else {
|
|
// 提取出来的多边形包含闭合的和非闭合的,在这里统一按照多边形处理
|
|
isHit = inPolygons(polygons, position.x, position.y) || inPolygons(polylines, position.x, position.y);
|
|
}
|
|
return isHit;
|
|
}
|
|
if (hasStroke || isClipPath) {
|
|
isHit = isPointInStroke(segments, lineWidth + increasedLineWidthForHitTesting, position.x, position.y, totalLength);
|
|
}
|
|
return isHit;
|
|
}
|
|
|
|
function isPointInPath$4(displayObject, position, isClipPath) {
|
|
var _ref = displayObject.parsedStyle,
|
|
stroke = _ref.stroke,
|
|
fill = _ref.fill,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
points = _ref.points,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents;
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents, fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasFill = _isFillOrStrokeAffect2[0],
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
var isHit = false;
|
|
if (hasStroke || isClipPath) {
|
|
isHit = inPolyline(points.points, lineWidth + increasedLineWidthForHitTesting, position.x, position.y, true);
|
|
}
|
|
if (!isHit && (hasFill || isClipPath)) {
|
|
isHit = inPolygon(points.points, position.x, position.y);
|
|
}
|
|
return isHit;
|
|
}
|
|
|
|
function isPointInPath$3(displayObject, position, isClipPath) {
|
|
var _ref = displayObject.parsedStyle,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
points = _ref.points,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents,
|
|
fill = _ref.fill,
|
|
stroke = _ref.stroke;
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents, fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
if (!hasStroke && !isClipPath || !lineWidth) {
|
|
return false;
|
|
}
|
|
return inPolyline(points.points, lineWidth + increasedLineWidthForHitTesting, position.x, position.y, false);
|
|
}
|
|
|
|
function isPointInPath$2(displayObject, position, isClipPath, isPointInPath, runtime) {
|
|
var _ref = displayObject.parsedStyle,
|
|
radius = _ref.radius,
|
|
fill = _ref.fill,
|
|
stroke = _ref.stroke,
|
|
_ref$lineWidth = _ref.lineWidth,
|
|
lineWidth = _ref$lineWidth === void 0 ? 1 : _ref$lineWidth,
|
|
_ref$increasedLineWid = _ref.increasedLineWidthForHitTesting,
|
|
increasedLineWidthForHitTesting = _ref$increasedLineWid === void 0 ? 0 : _ref$increasedLineWid,
|
|
_ref$x = _ref.x,
|
|
x = _ref$x === void 0 ? 0 : _ref$x,
|
|
_ref$y = _ref.y,
|
|
y = _ref$y === void 0 ? 0 : _ref$y,
|
|
width = _ref.width,
|
|
height = _ref.height,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents;
|
|
var _isFillOrStrokeAffect = gLite.isFillOrStrokeAffected(pointerEvents, fill, stroke),
|
|
_isFillOrStrokeAffect2 = _slicedToArray(_isFillOrStrokeAffect, 2),
|
|
hasFill = _isFillOrStrokeAffect2[0],
|
|
hasStroke = _isFillOrStrokeAffect2[1];
|
|
var hasRadius = radius && radius.some(function (r) {
|
|
return r !== 0;
|
|
});
|
|
var lineWidthForHitTesting = lineWidth + increasedLineWidthForHitTesting;
|
|
|
|
// 无圆角时的策略
|
|
if (!hasRadius) {
|
|
var halfWidth = lineWidthForHitTesting / 2;
|
|
// 同时填充和带有边框
|
|
if (hasFill && hasStroke || isClipPath) {
|
|
return inBox(x - halfWidth, y - halfWidth, width + halfWidth, height + halfWidth, position.x, position.y);
|
|
}
|
|
// 仅填充
|
|
if (hasFill) {
|
|
return inBox(x, y, width, height, position.x, position.y);
|
|
}
|
|
if (hasStroke) {
|
|
return inRect(x, y, width, height, lineWidthForHitTesting, position.x, position.y);
|
|
}
|
|
} else {
|
|
var isHit = false;
|
|
if (hasStroke || isClipPath) {
|
|
isHit = inRectWithRadius(x, y, width, height, radius.map(function (r) {
|
|
return util.clamp(r, 0, Math.min(Math.abs(width) / 2, Math.abs(height) / 2));
|
|
}), lineWidthForHitTesting, position.x, position.y);
|
|
}
|
|
// 仅填充时带有圆角的矩形直接通过图形拾取
|
|
// 以后可以改成纯数学的近似拾取,将圆弧切割成多边形
|
|
if (!isHit && (hasFill || isClipPath)) {
|
|
isHit = isPointInPath(displayObject, position);
|
|
}
|
|
return isHit;
|
|
}
|
|
return false;
|
|
}
|
|
function inRectWithRadius(minX, minY, width, height, radiusArray, lineWidth, x, y) {
|
|
var _radiusArray = _slicedToArray(radiusArray, 4),
|
|
tlr = _radiusArray[0],
|
|
trr = _radiusArray[1],
|
|
brr = _radiusArray[2],
|
|
blr = _radiusArray[3];
|
|
return inLine(minX + tlr, minY, minX + width - trr, minY, lineWidth, x, y) || inLine(minX + width, minY + trr, minX + width, minY + height - brr, lineWidth, x, y) || inLine(minX + width - brr, minY + height, minX + blr, minY + height, lineWidth, x, y) || inLine(minX, minY + height - blr, minX, minY + tlr, lineWidth, x, y) || inArc(minX + width - trr, minY + trr, trr, 1.5 * Math.PI, 2 * Math.PI, lineWidth, x, y) || inArc(minX + width - brr, minY + height - brr, brr, 0, 0.5 * Math.PI, lineWidth, x, y) || inArc(minX + blr, minY + height - blr, blr, 0.5 * Math.PI, Math.PI, lineWidth, x, y) || inArc(minX + tlr, minY + tlr, tlr, Math.PI, 1.5 * Math.PI, lineWidth, x, y);
|
|
}
|
|
|
|
function isPointInPath$1(displayObject, position, isClipPath, isPointInPath, renderingPluginContext, runtime) {
|
|
var _ref = displayObject.parsedStyle,
|
|
_ref$pointerEvents = _ref.pointerEvents,
|
|
pointerEvents = _ref$pointerEvents === void 0 ? 'auto' : _ref$pointerEvents,
|
|
_ref$x = _ref.x,
|
|
x = _ref$x === void 0 ? 0 : _ref$x,
|
|
_ref$y = _ref.y,
|
|
y = _ref$y === void 0 ? 0 : _ref$y,
|
|
width = _ref.width,
|
|
height = _ref.height;
|
|
if (pointerEvents === 'non-transparent-pixel') {
|
|
var offscreenCanvas = renderingPluginContext.config.offscreenCanvas;
|
|
var canvas = runtime.offscreenCanvasCreator.getOrCreateCanvas(offscreenCanvas);
|
|
var context = runtime.offscreenCanvasCreator.getOrCreateContext(offscreenCanvas, {
|
|
willReadFrequently: true
|
|
});
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
renderingPluginContext.defaultStyleRendererFactory[gLite.Shape.IMAGE].render(context, _objectSpread(_objectSpread({}, displayObject.parsedStyle), {}, {
|
|
x: 0,
|
|
y: 0
|
|
}), displayObject, undefined, undefined, undefined);
|
|
var imagedata = context.getImageData(position.x - x, position.y - y, 1, 1).data;
|
|
return imagedata.every(function (component) {
|
|
return component !== 0;
|
|
});
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function isPointInPath(displayObject, position, isClipPath, isPointInPath) {
|
|
var bounds = displayObject.getGeometryBounds();
|
|
|
|
// @see https://stackoverflow.com/questions/28706989/how-do-i-check-if-a-mouse-click-is-inside-a-rotated-text-on-the-html5-canvas-in
|
|
return position.x >= bounds.min[0] && position.y >= bounds.min[1] && position.x <= bounds.max[0] && position.y <= bounds.max[1];
|
|
}
|
|
|
|
var Plugin = /*#__PURE__*/function (_AbstractRendererPlug) {
|
|
function Plugin() {
|
|
var _this;
|
|
_classCallCheck(this, Plugin);
|
|
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
args[_key] = arguments[_key];
|
|
}
|
|
_this = _callSuper(this, Plugin, [].concat(args));
|
|
_this.name = 'canvas-picker';
|
|
return _this;
|
|
}
|
|
_inherits(Plugin, _AbstractRendererPlug);
|
|
return _createClass(Plugin, [{
|
|
key: "init",
|
|
value: function init() {
|
|
var _pointInPathPickerFac;
|
|
var pointInPathPickerFactory = (_pointInPathPickerFac = {}, _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_pointInPathPickerFac, gLite.Shape.CIRCLE, isPointInPath$8), gLite.Shape.ELLIPSE, isPointInPath$7), gLite.Shape.RECT, isPointInPath$2), gLite.Shape.LINE, isPointInPath$6), gLite.Shape.POLYLINE, isPointInPath$3), gLite.Shape.POLYGON, isPointInPath$4), gLite.Shape.PATH, isPointInPath$5), gLite.Shape.TEXT, isPointInPath), gLite.Shape.GROUP, null), gLite.Shape.IMAGE, isPointInPath$1), _defineProperty(_defineProperty(_pointInPathPickerFac, gLite.Shape.HTML, null), gLite.Shape.MESH, null));
|
|
|
|
// @ts-ignore
|
|
this.context.pointInPathPickerFactory = pointInPathPickerFactory;
|
|
this.addRenderingPlugin(new CanvasPickerPlugin());
|
|
}
|
|
}, {
|
|
key: "destroy",
|
|
value: function destroy() {
|
|
// @ts-ignore
|
|
delete this.context.pointInPathPickerFactory;
|
|
this.removeAllRenderingPlugins();
|
|
}
|
|
}]);
|
|
}(gLite.AbstractRendererPlugin);
|
|
|
|
exports.Plugin = Plugin;
|
|
//# sourceMappingURL=index.js.map
|
|
|