Merge branch 'alesmenzel-am/add-fill-and-clip-options-support' into master

This commit is contained in:
Anton Lavrenov 2023-06-05 10:25:46 -05:00
commit caf161a676
8 changed files with 122 additions and 22 deletions

View File

@ -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:watch": "rm -rf ./.parcel-cache && parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html ./test/bunnies.html",
"test:watch": "rm -rf ./parcel-cache && parcel serve ./test/unit-tests.html ./test/manual-tests.html ./test/sandbox.html ./test/text-paths.html", "test:node": "ts-mocha -r ./test/node-global-setup.mjs -p ./test/tsconfig.json test/unit/**/*.ts --exit && npm run test:import",
"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",

View File

@ -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']
* }); * });
*/ */

View File

@ -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,11 @@ 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();
this._context.fill(); this._context.fill.apply(this._context, args);
}
} }
/** /**
* fillRect function. * fillRect function.
@ -749,7 +750,7 @@ export class Context {
// supported context properties // supported context properties
type CanvasContextProps = Pick< type CanvasContextProps = Pick<
CanvasRenderingContext2D, CanvasRenderingContext2D,
typeof CONTEXT_PROPERTIES[number] (typeof CONTEXT_PROPERTIES)[number]
>; >;
export interface Context extends CanvasContextProps {} export interface Context extends CanvasContextProps {}

View File

@ -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,11 @@ 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 +118,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.attrs.fillRule;
if (fillRule) {
context.fill(fillRule);
} else {
context.fill();
}
} }
function _strokeFunc(context) { function _strokeFunc(context) {
context.stroke(); context.stroke();
@ -176,7 +187,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 +1998,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',

View File

@ -10,12 +10,9 @@
</head> </head>
<body> <body>
<div id="container"></div> <div id="container"></div>
<!-- this line for dev version -->
<script src="../../src/index.ts"></script>
<!-- this line for old version -->
<!-- <script src="https://unpkg.com/konva@7.0.3/konva.min.js"></script> -->
<script src="http://www.html5canvastutorials.com/lib/stats/stats.js"></script> <script src="http://www.html5canvastutorials.com/lib/stats/stats.js"></script>
<script defer="defer"> <script type="module">
import Konva from '../src/index.ts';
Konva.isUnminified = false; Konva.isUnminified = false;
// Konva.autoDrawEnabled = trye; // Konva.autoDrawEnabled = trye;
var lastTime = 0; var lastTime = 0;
@ -99,6 +96,7 @@
perfectDrawEnabled: false, perfectDrawEnabled: false,
width: wabbitTexture.width, width: wabbitTexture.width,
height: wabbitTexture.height, height: wabbitTexture.height,
fill: 'yellow',
}); });
bunny.speedX = Math.random() * 10; bunny.speedX = Math.random() * 10;

View File

@ -0,0 +1,11 @@
export function mochaGlobalSetup() {
globalThis.Path2D ??= class Path2D {
constructor(path) {
this.path = path
}
get [Symbol.toStringTag]() {
return `Path2D`;
}
}
}

View File

@ -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();');
});
}); });

View File

@ -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();');
});
}); });