From 8694dfc8b6603df64c6fa2391054e9ce2176fcc4 Mon Sep 17 00:00:00 2001 From: xiaoiver Date: Sat, 23 Nov 2024 23:15:54 +0800 Subject: [PATCH] fix: visibility on group --- .../ssr/snapshots/visibility-group-hide.png | Bin 0 -> 3360 bytes .../ssr/snapshots/visibility-group-hide.svg | 41 ++++ .../ssr/snapshots/visibility-group-show.png | Bin 0 -> 3360 bytes .../ssr/snapshots/visibility-group-show.svg | 41 ++++ __tests__/ssr/visibility.spec.ts | 48 ++++- packages/core/examples/main.ts | 26 ++- packages/core/src/Canvas.ts | 2 +- packages/core/src/plugins/Renderer.ts | 6 +- packages/core/src/plugins/Selector.ts | 3 +- .../shapes/selectable/AbstractSelectable.ts | 178 +++++++++++++++++- .../src/shapes/selectable/SelectableRect.ts | 91 ++++++--- 11 files changed, 398 insertions(+), 38 deletions(-) create mode 100644 __tests__/ssr/snapshots/visibility-group-hide.png create mode 100644 __tests__/ssr/snapshots/visibility-group-hide.svg create mode 100644 __tests__/ssr/snapshots/visibility-group-show.png create mode 100644 __tests__/ssr/snapshots/visibility-group-show.svg diff --git a/__tests__/ssr/snapshots/visibility-group-hide.png b/__tests__/ssr/snapshots/visibility-group-hide.png new file mode 100644 index 0000000000000000000000000000000000000000..11ed7646eb0fc77b4887de90ad82666bf377fd56 GIT binary patch literal 3360 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF?qT;hEy=Vk*d4Iz`%18#CvsNLj>@tpZ@-R{;9;ZUrQpgZDp@T`L}Pqb{Wd8 z{pkEucq%6v)ST)cqJ{?TEZ1zEx9`Wq7Gf-{*59xC&=s!_Kn{NR^qx1!SibDF%MVAr ze!1aVl@$`VTV8Aa3LkX1vSyLdLR9$aB+xrwj`pkU!3-(%fZ21W`ll|ZJ|Za56~IDT z|9c#cKm&zg{GWiIE~!pv&RQ5RFc?c<))WDgMD0gqBEzH3`w%fnbMJJJv1lm`#uc{@u?w6TfChsw1M}%*P?G4D zCo()fMGM8D<`LuogJq$(cYCK`Noml8x9`WoPo1Y)(BKp-fjM6WoCZ9I43GPhJ}knS zmtpSwQ1pH&$XKN82@5oE^5(~qE0;L^Bsu249d-i=2sv#WdVl*@1tiY#N>*EMCNL?|SnXuR6qwNLM zW(zPpM%xRpCfI0u0nz3Xx8Jva|Ia7jN(8OWfYvTofc=O0Xw?m}%AZgEGd{eUv3#$w Sdm6CI!{F)a=d#Wzp$P!&?gTvm literal 0 HcmV?d00001 diff --git a/__tests__/ssr/snapshots/visibility-group-hide.svg b/__tests__/ssr/snapshots/visibility-group-hide.svg new file mode 100644 index 0000000..2691d6d --- /dev/null +++ b/__tests__/ssr/snapshots/visibility-group-hide.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/__tests__/ssr/snapshots/visibility-group-show.png b/__tests__/ssr/snapshots/visibility-group-show.png new file mode 100644 index 0000000000000000000000000000000000000000..11ed7646eb0fc77b4887de90ad82666bf377fd56 GIT binary patch literal 3360 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF?qT;hEy=Vk*d4Iz`%18#CvsNLj>@tpZ@-R{;9;ZUrQpgZDp@T`L}Pqb{Wd8 z{pkEucq%6v)ST)cqJ{?TEZ1zEx9`Wq7Gf-{*59xC&=s!_Kn{NR^qx1!SibDF%MVAr ze!1aVl@$`VTV8Aa3LkX1vSyLdLR9$aB+xrwj`pkU!3-(%fZ21W`ll|ZJ|Za56~IDT z|9c#cKm&zg{GWiIE~!pv&RQ5RFc?c<))WDgMD0gqBEzH3`w%fnbMJJJv1lm`#uc{@u?w6TfChsw1M}%*P?G4D zCo()fMGM8D<`LuogJq$(cYCK`Noml8x9`WoPo1Y)(BKp-fjM6WoCZ9I43GPhJ}knS zmtpSwQ1pH&$XKN82@5oE^5(~qE0;L^Bsu249d-i=2sv#WdVl*@1tiY#N>*EMCNL?|SnXuR6qwNLM zW(zPpM%xRpCfI0u0nz3Xx8Jva|Ia7jN(8OWfYvTofc=O0Xw?m}%AZgEGd{eUv3#$w Sdm6CI!{F)a=d#Wzp$P!&?gTvm literal 0 HcmV?d00001 diff --git a/__tests__/ssr/snapshots/visibility-group-show.svg b/__tests__/ssr/snapshots/visibility-group-show.svg new file mode 100644 index 0000000..28520cf --- /dev/null +++ b/__tests__/ssr/snapshots/visibility-group-show.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/__tests__/ssr/visibility.spec.ts b/__tests__/ssr/visibility.spec.ts index 4ed71c0..bc28105 100644 --- a/__tests__/ssr/visibility.spec.ts +++ b/__tests__/ssr/visibility.spec.ts @@ -3,7 +3,7 @@ import { JSDOM } from 'jsdom'; import xmlserializer from 'xmlserializer'; import { getCanvas, sleep } from '../utils'; import '../useSnapshotMatchers'; -import { Canvas, ImageExporter, Rect } from '../../packages/core/src'; +import { Canvas, ImageExporter, Rect, Group } from '../../packages/core/src'; const dir = `${__dirname}/snapshots`; let $canvas: HTMLCanvasElement; @@ -90,4 +90,50 @@ describe('Visibility', () => { 'visibility-hide', ); }); + + it('should account for visibility in group correctly.', async () => { + const group = new Group(); + const rect1 = new Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'red', + visible: false, + }); + group.appendChild(rect1); + const rect2 = new Rect({ + x: 50, + y: 50, + width: 100, + height: 100, + fill: 'green', + }); + group.appendChild(rect2); + canvas.appendChild(group); + group.visible = false; + canvas.render(); + + expect($canvas.getContext('webgl1')).toMatchWebGLSnapshot( + dir, + 'visibility-group-hide', + ); + expect(exporter.toSVG({ grid: true })).toMatchSVGSnapshot( + dir, + 'visibility-group-hide', + ); + + await sleep(300); + + group.visible = true; + canvas.render(); + expect($canvas.getContext('webgl1')).toMatchWebGLSnapshot( + dir, + 'visibility-group-show', + ); + expect(exporter.toSVG({ grid: true })).toMatchSVGSnapshot( + dir, + 'visibility-group-show', + ); + }); }); diff --git a/packages/core/examples/main.ts b/packages/core/examples/main.ts index cd5b409..fc58f2d 100644 --- a/packages/core/examples/main.ts +++ b/packages/core/examples/main.ts @@ -1,6 +1,6 @@ -import { set } from '@antv/util'; import { Canvas, + Group, Circle, Rect, RoughCircle, @@ -33,17 +33,33 @@ const canvas = await new Canvas({ shaderCompilerPath: '/glsl_wgsl_compiler_bg.wasm', }).initialized; -const rect = new RoughRect({ +const group = new Group(); +const rect1 = new Rect({ + x: 0, + y: 0, + width: 100, + height: 100, + fill: 'red', + visible: false, +}); +group.appendChild(rect1); +const rect2 = new Rect({ x: 50, y: 50, width: 100, height: 100, - fill: 'black', - // fillStyle: 'dots', + fill: 'green', }); -canvas.appendChild(rect); +group.appendChild(rect2); +canvas.appendChild(group); +group.visible = false; canvas.render(); +setTimeout(() => { + group.visible = true; + canvas.render(); +}, 2000); + // console.log(toSVGElement(serializeNode(rect1))); // const animate = () => { diff --git a/packages/core/src/Canvas.ts b/packages/core/src/Canvas.ts index fde0547..c986df7 100644 --- a/packages/core/src/Canvas.ts +++ b/packages/core/src/Canvas.ts @@ -242,7 +242,7 @@ export class Canvas { shape.renderDirtyFlag = true; } - if (shape.renderable && shape.renderDirtyFlag) { + if (shape.renderDirtyFlag) { modified.push(shape); this.#renderDirtyFlag = true; } diff --git a/packages/core/src/plugins/Renderer.ts b/packages/core/src/plugins/Renderer.ts index 51382f8..f446b22 100644 --- a/packages/core/src/plugins/Renderer.ts +++ b/packages/core/src/plugins/Renderer.ts @@ -282,8 +282,10 @@ export class Renderer implements Plugin { }); hooks.render.tap((shape) => { - shape.globalRenderOrder = this.#zIndexCounter++; - this.#batchManager.add(shape); + if (shape.renderable) { + shape.globalRenderOrder = this.#zIndexCounter++; + this.#batchManager.add(shape); + } }); } diff --git a/packages/core/src/plugins/Selector.ts b/packages/core/src/plugins/Selector.ts index f8399b8..c3d8368 100644 --- a/packages/core/src/plugins/Selector.ts +++ b/packages/core/src/plugins/Selector.ts @@ -7,7 +7,6 @@ import { Plugin, PluginContext } from './interfaces'; export class Selector implements Plugin { #selected: Shape[] = []; #selectableMap: Record = {}; - #enableContinuousBrush = true; /** * the topmost operation layer, which will be appended to documentElement directly @@ -31,7 +30,7 @@ export class Selector implements Plugin { const selected = e.target as Shape; if (selected === root) { - if (!e.shiftKey || (e.shiftKey && !this.#enableContinuousBrush)) { + if (!e.shiftKey) { this.deselectAllShapes(); this.#selected = []; } diff --git a/packages/core/src/shapes/selectable/AbstractSelectable.ts b/packages/core/src/shapes/selectable/AbstractSelectable.ts index 4c1d256..507da71 100644 --- a/packages/core/src/shapes/selectable/AbstractSelectable.ts +++ b/packages/core/src/shapes/selectable/AbstractSelectable.ts @@ -3,18 +3,194 @@ import { Shape, ShapeAttributes } from '../Shape'; export interface AbstractSelectableAttribtues extends ShapeAttributes { target: Shape; + maskFill: string; + maskFillOpacity: number; + maskStroke: string; + maskStrokeOpacity: number; + maskStrokeWidth: number; + maskOpacity: number; + + anchorFill: string; + anchorFillOpacity: number; + anchorSize: number; + anchorStroke: string; + anchorStrokeOpacity: number; + anchorStrokeWidth: number; + anchorOpacity: number; } export abstract class AbstractSelectable extends Group { protected target: Shape; + #maskFill: string; + #maskFillOpacity: number; + #maskStroke: string; + #maskStrokeOpacity: number; + #maskStrokeWidth: number; + #maskOpacity: number; + + #anchorSize: number; + #anchorStroke: string; + #anchorFill: string; + #anchorFillOpacity: number; + #anchorStrokeOpacity: number; + #anchorStrokeWidth: number; + #anchorOpacity: number; + constructor(attributes: Partial) { super(attributes); - this.target = attributes.target; + const { + target, + maskFill, + maskFillOpacity, + maskStroke, + maskStrokeOpacity, + maskStrokeWidth, + maskOpacity, + anchorSize, + anchorStroke, + anchorFill, + anchorFillOpacity, + anchorStrokeOpacity, + anchorStrokeWidth, + anchorOpacity, + } = attributes; + + this.target = target; + this.maskFill = maskFill ?? 'none'; + this.maskFillOpacity = maskFillOpacity ?? 1.0; + this.maskStroke = maskStroke ?? 'black'; + this.maskStrokeOpacity = maskStrokeOpacity ?? 1.0; + this.maskStrokeWidth = maskStrokeWidth ?? 1; + this.maskOpacity = maskOpacity ?? 1.0; + this.anchorSize = anchorSize ?? 4; + this.anchorStroke = anchorStroke ?? 'black'; + this.anchorFill = anchorFill ?? 'none'; + this.anchorFillOpacity = anchorFillOpacity ?? 1; + this.anchorStrokeOpacity = anchorStrokeOpacity ?? 1; + this.anchorStrokeWidth = anchorStrokeWidth ?? 1; + this.anchorOpacity = anchorOpacity ?? 1; this.init(); } protected abstract init(): void; + + get anchorSize() { + return this.#anchorSize; + } + set anchorSize(anchorSize: number) { + if (this.#anchorSize !== anchorSize) { + this.#anchorSize = anchorSize; + } + } + + get anchorStroke() { + return this.#anchorStroke; + } + set anchorStroke(anchorStroke: string) { + if (this.#anchorStroke !== anchorStroke) { + this.#anchorStroke = anchorStroke; + } + } + + get anchorFill() { + return this.#anchorFill; + } + set anchorFill(anchorFill: string) { + if (this.#anchorFill !== anchorFill) { + this.#anchorFill = anchorFill; + } + } + + get anchorFillOpacity() { + return this.#anchorFillOpacity; + } + set anchorFillOpacity(anchorFillOpacity: number) { + if (this.#anchorFillOpacity !== anchorFillOpacity) { + this.#anchorFillOpacity = anchorFillOpacity; + } + } + + get anchorStrokeOpacity() { + return this.#anchorStrokeOpacity; + } + set anchorStrokeOpacity(anchorStrokeOpacity: number) { + if (this.#anchorStrokeOpacity !== anchorStrokeOpacity) { + this.#anchorStrokeOpacity = anchorStrokeOpacity; + } + } + + get anchorStrokeWidth() { + return this.#anchorStrokeWidth; + } + set anchorStrokeWidth(anchorStrokeWidth: number) { + if (this.#anchorStrokeWidth !== anchorStrokeWidth) { + this.#anchorStrokeWidth = anchorStrokeWidth; + } + } + + get anchorOpacity() { + return this.#anchorFillOpacity; + } + set anchorOpacity(anchorOpacity: number) { + if (this.#anchorOpacity !== anchorOpacity) { + this.#anchorOpacity = anchorOpacity; + } + } + + get maskFill() { + return this.#maskFill; + } + set maskFill(maskFill: string) { + if (this.#maskFill !== maskFill) { + this.#maskFill = maskFill; + } + } + + get maskFillOpacity() { + return this.#maskFillOpacity; + } + set maskFillOpacity(maskFillOpacity: number) { + if (this.#maskFillOpacity !== maskFillOpacity) { + this.#maskFillOpacity = maskFillOpacity; + } + } + + get maskStroke() { + return this.#maskStroke; + } + set maskStroke(maskStroke: string) { + if (this.#maskStroke !== maskStroke) { + this.#maskStroke = maskStroke; + } + } + + get maskStrokeOpacity() { + return this.#maskStrokeOpacity; + } + set maskStrokeOpacity(maskStrokeOpacity: number) { + if (this.#maskStrokeOpacity !== maskStrokeOpacity) { + this.#maskStrokeOpacity = maskStrokeOpacity; + } + } + + get maskStrokeWidth() { + return this.#maskStrokeWidth; + } + set maskStrokeWidth(maskStrokeWidth: number) { + if (this.#maskStrokeWidth !== maskStrokeWidth) { + this.#maskStrokeWidth = maskStrokeWidth; + } + } + + get maskOpacity() { + return this.#maskOpacity; + } + set maskOpacity(maskOpacity: number) { + if (this.#maskOpacity !== maskOpacity) { + this.#maskOpacity = maskOpacity; + } + } } diff --git a/packages/core/src/shapes/selectable/SelectableRect.ts b/packages/core/src/shapes/selectable/SelectableRect.ts index e654227..45569f1 100644 --- a/packages/core/src/shapes/selectable/SelectableRect.ts +++ b/packages/core/src/shapes/selectable/SelectableRect.ts @@ -1,22 +1,23 @@ -// import { Circle } from '../Circle'; +import { AABB } from '../AABB'; +import { Circle } from '../Circle'; import { Rect } from '../Rect'; import { AbstractSelectable } from './AbstractSelectable'; export class SelectableRect extends AbstractSelectable { mask: Rect; - // #tlAnchor: Circle; - // #trAnchor: Circle; - // #blAnchor: Circle; - // #brAnchor: Circle; + tlAnchor: Circle; + trAnchor: Circle; + blAnchor: Circle; + brAnchor: Circle; init() { // account for world transform - // const bounds = new AABB(); - // bounds.addBounds( - // this.target.getGeometryBounds(), - // this.target.worldTransform, - // ); - const bounds = this.target.getBounds(); + const bounds = new AABB(); + bounds.addBounds( + this.target.getGeometryBounds(), + this.target.worldTransform, + ); + // const bounds = this.target.getBounds(); const { minX, minY, maxX, maxY } = bounds; this.mask = new Rect({ @@ -24,26 +25,64 @@ export class SelectableRect extends AbstractSelectable { y: minY, width: maxX - minX, height: maxY - minY, - fill: 'red', - opacity: 0.5, + fill: this.maskFill, + fillOpacity: this.maskFillOpacity, + stroke: this.maskStroke, + strokeOpacity: this.maskStrokeOpacity, + strokeWidth: this.maskStrokeWidth, + opacity: this.maskOpacity, cursor: 'move', sizeAttenuation: true, batchable: false, + cullable: false, }); this.appendChild(this.mask); - // this.#tlAnchor = new Circle({ - // r: anchorSize, - // cursor: 'nwse-resize', - // draggable: true, - // isSizeAttenuation: true, - // stroke: anchorStroke, - // fill: anchorFill, - // fillOpacity: anchorFillOpacity, - // strokeOpacity: anchorStrokeOpacity, - // lineWidth: anchorStrokeWidth, - // batchable: false, - // cullable: false, - // }); + const anchorAttributes = { + r: this.anchorSize, + draggable: true, + sizeAttenuation: true, + stroke: this.anchorStroke, + fill: this.anchorFill, + fillOpacity: this.anchorFillOpacity, + strokeOpacity: this.anchorStrokeOpacity, + strokeWidth: this.anchorStrokeWidth, + opacity: this.anchorOpacity, + batchable: false, + cullable: false, + }; + + this.tlAnchor = new Circle({ + cx: minX, + cy: minY, + cursor: 'nwse-resize', + ...anchorAttributes, + }); + + this.trAnchor = new Circle({ + cx: maxX, + cy: minY, + cursor: 'nesw-resize', + ...anchorAttributes, + }); + + this.blAnchor = new Circle({ + cx: minX, + cy: maxY, + cursor: 'nesw-resize', + ...anchorAttributes, + }); + + this.brAnchor = new Circle({ + cx: maxX, + cy: maxY, + cursor: 'nwse-resize', + ...anchorAttributes, + }); + + this.mask.appendChild(this.tlAnchor); + this.mask.appendChild(this.trAnchor); + this.mask.appendChild(this.blAnchor); + this.mask.appendChild(this.brAnchor); } }