mirror of
https://github.com/konvajs/konva.git
synced 2025-06-28 04:42:02 +08:00
support context.clip(...) and context.fill(...) Path2D and fill-rule options
This commit is contained in:
parent
3c0c6c8a3e
commit
95048b5086
@ -20,8 +20,8 @@
|
|||||||
"test": "npm run test:browser && npm run test:node",
|
"test": "npm run test:browser && npm run test:node",
|
||||||
"test:build": "parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps",
|
"test:build": "parcel build ./test/unit-tests.html --dist-dir ./test-build --target none --public-url ./ --no-source-maps",
|
||||||
"test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security",
|
"test:browser": "npm run test:build && mocha-headless-chrome -f ./test-build/unit-tests.html -a disable-web-security",
|
||||||
"test:node": "ts-mocha -p ./test/tsconfig.json test/unit/**/*.ts --exit && npm run test:import",
|
"test:node": "ts-mocha -r ./test/node-global-setup.mjs -p ./test/tsconfig.json test/unit/**/*.ts --exit && npm run test:import",
|
||||||
"test:watch": "rm -rf ./parcel-cache && parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html",
|
"test:watch": "rm -rf ./.parcel-cache && parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html",
|
||||||
"tsc": "tsc --removeComments",
|
"tsc": "tsc --removeComments",
|
||||||
"rollup": "rollup -c --bundleConfigAsCjs",
|
"rollup": "rollup -c --bundleConfigAsCjs",
|
||||||
"clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build",
|
"clean": "rm -rf ./lib && rm -rf ./types && rm -rf ./cmj && rm -rf ./test-build",
|
||||||
|
@ -7,9 +7,10 @@ import { Shape } from './Shape';
|
|||||||
import { HitCanvas, SceneCanvas } from './Canvas';
|
import { HitCanvas, SceneCanvas } from './Canvas';
|
||||||
import { SceneContext } from './Context';
|
import { SceneContext } from './Context';
|
||||||
|
|
||||||
|
export type ClipFuncOutput = void | [Path2D | CanvasFillRule] | [Path2D, CanvasFillRule]
|
||||||
export interface ContainerConfig extends NodeConfig {
|
export interface ContainerConfig extends NodeConfig {
|
||||||
clearBeforeDraw?: boolean;
|
clearBeforeDraw?: boolean;
|
||||||
clipFunc?: (ctx: SceneContext) => void;
|
clipFunc?: (ctx: SceneContext) => ClipFuncOutput;
|
||||||
clipX?: number;
|
clipX?: number;
|
||||||
clipY?: number;
|
clipY?: number;
|
||||||
clipWidth?: number;
|
clipWidth?: number;
|
||||||
@ -396,14 +397,15 @@ export abstract class Container<
|
|||||||
var m = transform.getMatrix();
|
var m = transform.getMatrix();
|
||||||
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
|
let clipArgs;
|
||||||
if (clipFunc) {
|
if (clipFunc) {
|
||||||
clipFunc.call(this, context, this);
|
clipArgs = clipFunc.call(this, context, this);
|
||||||
} else {
|
} else {
|
||||||
var clipX = this.clipX();
|
var clipX = this.clipX();
|
||||||
var clipY = this.clipY();
|
var clipY = this.clipY();
|
||||||
context.rect(clipX, clipY, clipWidth, clipHeight);
|
context.rect(clipX, clipY, clipWidth, clipHeight);
|
||||||
}
|
}
|
||||||
context.clip();
|
context.clip.apply(context, clipArgs);
|
||||||
m = transform.copy().invert().getMatrix();
|
m = transform.copy().invert().getMatrix();
|
||||||
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||||
}
|
}
|
||||||
@ -519,7 +521,7 @@ export abstract class Container<
|
|||||||
// there was "this" instead of "Container<ChildType>",
|
// there was "this" instead of "Container<ChildType>",
|
||||||
// but it breaks react-konva types: https://github.com/konvajs/react-konva/issues/390
|
// but it breaks react-konva types: https://github.com/konvajs/react-konva/issues/390
|
||||||
clipFunc: GetSet<
|
clipFunc: GetSet<
|
||||||
(ctx: CanvasRenderingContext2D, shape: Container<ChildType>) => void,
|
(ctx: CanvasRenderingContext2D, shape: Container<ChildType>) => ClipFuncOutput,
|
||||||
this
|
this
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
@ -638,5 +640,8 @@ Factory.addGetterSetter(Container, 'clipFunc');
|
|||||||
* // set clip height
|
* // set clip height
|
||||||
* container.clipFunc(function(ctx) {
|
* container.clipFunc(function(ctx) {
|
||||||
* ctx.rect(0, 0, 100, 100);
|
* ctx.rect(0, 0, 100, 100);
|
||||||
|
*
|
||||||
|
* // optionally return a clip Path2D and clip-rule or just the clip-rule
|
||||||
|
* return [new Path2D('M0 0v50h50Z'), 'evenodd']
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
@ -362,8 +362,10 @@ export class Context {
|
|||||||
* @method
|
* @method
|
||||||
* @name Konva.Context#clip
|
* @name Konva.Context#clip
|
||||||
*/
|
*/
|
||||||
clip() {
|
clip(fillRule?: CanvasFillRule): void
|
||||||
this._context.clip();
|
clip(path: Path2D, fillRule?: CanvasFillRule): void
|
||||||
|
clip(...args: any[]) {
|
||||||
|
this._context.clip.apply(this._context, args);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* closePath function.
|
* closePath function.
|
||||||
@ -482,12 +484,10 @@ export class Context {
|
|||||||
* @method
|
* @method
|
||||||
* @name Konva.Context#fill
|
* @name Konva.Context#fill
|
||||||
*/
|
*/
|
||||||
fill(path2d?: Path2D) {
|
fill(fillRule?: CanvasFillRule): void
|
||||||
if (path2d) {
|
fill(path: Path2D, fillRule?: CanvasFillRule): void
|
||||||
this._context.fill(path2d);
|
fill(...args: any[]) {
|
||||||
} else {
|
this._context.fill.apply(this._context, args);
|
||||||
this._context.fill();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* fillRect function.
|
* fillRect function.
|
||||||
|
28
src/Shape.ts
28
src/Shape.ts
@ -56,6 +56,7 @@ export interface ShapeConfig extends NodeConfig {
|
|||||||
fillRadialGradientColorStops?: Array<number | string>;
|
fillRadialGradientColorStops?: Array<number | string>;
|
||||||
fillEnabled?: boolean;
|
fillEnabled?: boolean;
|
||||||
fillPriority?: string;
|
fillPriority?: string;
|
||||||
|
fillRule?: CanvasFillRule
|
||||||
stroke?: string | CanvasGradient;
|
stroke?: string | CanvasGradient;
|
||||||
strokeWidth?: number;
|
strokeWidth?: number;
|
||||||
fillAfterStrokeEnabled?: boolean;
|
fillAfterStrokeEnabled?: boolean;
|
||||||
@ -88,6 +89,8 @@ export interface ShapeGetClientRectConfig {
|
|||||||
relativeTo?: Node;
|
relativeTo?: Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FillFuncOutput = void | [Path2D | CanvasFillRule] | [Path2D, CanvasFillRule]
|
||||||
|
|
||||||
var HAS_SHADOW = 'hasShadow';
|
var HAS_SHADOW = 'hasShadow';
|
||||||
var SHADOW_RGBA = 'shadowRGBA';
|
var SHADOW_RGBA = 'shadowRGBA';
|
||||||
var patternImage = 'patternImage';
|
var patternImage = 'patternImage';
|
||||||
@ -112,7 +115,12 @@ export const shapes: { [key: string]: Shape } = {};
|
|||||||
// what color to use for hit test?
|
// what color to use for hit test?
|
||||||
|
|
||||||
function _fillFunc(context) {
|
function _fillFunc(context) {
|
||||||
context.fill();
|
const fillRule = this.fillRule()
|
||||||
|
if (fillRule) {
|
||||||
|
context.fill(fillRule);
|
||||||
|
} else {
|
||||||
|
context.fill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function _strokeFunc(context) {
|
function _strokeFunc(context) {
|
||||||
context.stroke();
|
context.stroke();
|
||||||
@ -176,7 +184,7 @@ export class Shape<
|
|||||||
_centroid: boolean;
|
_centroid: boolean;
|
||||||
colorKey: string;
|
colorKey: string;
|
||||||
|
|
||||||
_fillFunc: (ctx: Context) => void;
|
_fillFunc: (ctx: Context) => FillFuncOutput;
|
||||||
_strokeFunc: (ctx: Context) => void;
|
_strokeFunc: (ctx: Context) => void;
|
||||||
_fillFuncHit: (ctx: Context) => void;
|
_fillFuncHit: (ctx: Context) => void;
|
||||||
_strokeFuncHit: (ctx: Context) => void;
|
_strokeFuncHit: (ctx: Context) => void;
|
||||||
@ -1987,6 +1995,22 @@ Factory.addGetterSetter(Shape, 'fillPatternRotation', 0);
|
|||||||
* shape.fillPatternRotation(20);
|
* shape.fillPatternRotation(20);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
Factory.addGetterSetter(Shape, 'fillRule', undefined, getStringValidator());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get/set fill rule
|
||||||
|
* @name Konva.Shape#fillRule
|
||||||
|
* @method
|
||||||
|
* @param {CanvasFillRule} rotation
|
||||||
|
* @returns {Konva.Shape}
|
||||||
|
* @example
|
||||||
|
* // get fill rule
|
||||||
|
* var fillRule = shape.fillRule();
|
||||||
|
*
|
||||||
|
* // set fill rule
|
||||||
|
* shape.fillRule('evenodd);
|
||||||
|
*/
|
||||||
|
|
||||||
Factory.backCompat(Shape, {
|
Factory.backCompat(Shape, {
|
||||||
dashArray: 'dash',
|
dashArray: 'dash',
|
||||||
getDashArray: 'getDash',
|
getDashArray: 'getDash',
|
||||||
|
11
test/node-global-setup.mjs
Normal file
11
test/node-global-setup.mjs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export function mochaGlobalSetup() {
|
||||||
|
globalThis.Path2D ??= class Path2D {
|
||||||
|
constructor(path) {
|
||||||
|
this.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
get [Symbol.toStringTag]() {
|
||||||
|
return `Path2D`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { addStage, cloneAndCompareLayer, Konva } from './test-utils';
|
import { addStage, cloneAndCompareLayer, Konva } from './test-utils';
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
describe('Group', function () {
|
describe('Group', function () {
|
||||||
// ======================================================
|
// ======================================================
|
||||||
@ -45,4 +46,42 @@ describe('Group', function () {
|
|||||||
|
|
||||||
cloneAndCompareLayer(layer, 200);
|
cloneAndCompareLayer(layer, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clip group with a Path2D', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
|
||||||
|
var path = new Konva.Group({
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
clipFunc: () => [new Path2D('M0 0v50h50Z')]
|
||||||
|
});
|
||||||
|
|
||||||
|
layer.add(path);
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
const trace = layer.getContext().getTrace()
|
||||||
|
|
||||||
|
assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D]);transform(1,0,0,1,0,0);restore();');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clip group with a Path2D and clipRule', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
|
||||||
|
var path = new Konva.Group({
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
clipFunc: () => [new Path2D('M0 0v50h50Z'), 'evenodd'],
|
||||||
|
});
|
||||||
|
|
||||||
|
layer.add(path);
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
const trace = layer.getContext().getTrace()
|
||||||
|
|
||||||
|
assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();clip([object Path2D],evenodd);transform(1,0,0,1,0,0);restore();');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1603,4 +1603,23 @@ describe('Path', function () {
|
|||||||
|
|
||||||
assert.equal(trace1, trace2);
|
assert.equal(trace1, trace2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('draw path with fillRule', function () {
|
||||||
|
var stage = addStage();
|
||||||
|
|
||||||
|
var layer = new Konva.Layer();
|
||||||
|
|
||||||
|
var path = new Konva.Path({
|
||||||
|
data: 'M200,100h100v50z',
|
||||||
|
fill: '#ccc',
|
||||||
|
fillRule: 'evenodd',
|
||||||
|
});
|
||||||
|
|
||||||
|
layer.add(path);
|
||||||
|
stage.add(layer);
|
||||||
|
|
||||||
|
const trace = layer.getContext().getTrace()
|
||||||
|
|
||||||
|
assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user