From 381b2c083d90aa4fe815370afd0138dde114bf4a Mon Sep 17 00:00:00 2001 From: aarthificial Date: Tue, 22 Mar 2022 22:28:14 +0100 Subject: [PATCH] feat: add layered layout --- src/components/Debug.ts | 55 +++++++++++++++++++++++++++++---- src/components/LayeredLayout.ts | 54 ++++++++++++++++++++++++++++++++ src/components/Layout.ts | 19 ++++++++---- src/components/LayoutGroup.ts | 13 +------- src/components/Surface.ts | 31 ++++++++++++------- src/types/Spacing.ts | 30 +++++++++++++++--- 6 files changed, 162 insertions(+), 40 deletions(-) create mode 100644 src/components/LayeredLayout.ts diff --git a/src/components/Debug.ts b/src/components/Debug.ts index 9b771505..74ca6ae5 100644 --- a/src/components/Debug.ts +++ b/src/components/Debug.ts @@ -1,6 +1,7 @@ import {Shape, ShapeConfig} from 'konva/lib/Shape'; import {Node} from 'konva/lib/Node'; import {Context} from 'konva/lib/Context'; +import {isLayoutNode} from 'MC/components/ILayoutNode'; export interface DebugConfig extends ShapeConfig { target: Node; @@ -20,12 +21,54 @@ export class Debug extends Shape { } _sceneFunc(context: Context) { - const rect = this.getTarget().getClientRect({relativeTo: this.getLayer()}); - const position = this.getTarget().getAbsolutePosition(this.getLayer()); + const target = this.getTarget(); + const rect = target.getClientRect({relativeTo: this.getLayer()}); + const position = target.getAbsolutePosition(this.getLayer()); - context.rect(rect.x, rect.y, rect.width, rect.height); - context.moveTo(position.x, position.y); - context.arc(position.x, position.y, 4, 0, Math.PI * 2, false); - context.fillStrokeShape(this); + if (isLayoutNode(target)) { + const ctx = context._context; + const contentRect = target.getPadd().shrink(rect); + const marginRect = target.getMargin().expand(rect); + + ctx.beginPath(); + ctx.rect( + contentRect.x, + contentRect.y, + contentRect.width, + contentRect.height, + ); + ctx.rect(rect.x, rect.y, rect.width, rect.height); + ctx.closePath(); + ctx.fillStyle = 'rgba(180,255,147,0.5)'; + ctx.fill('evenodd'); + + ctx.beginPath(); + ctx.rect(rect.x, rect.y, rect.width, rect.height); + ctx.closePath(); + ctx.strokeStyle = 'rgba(255, 255, 255, 1)'; + ctx.stroke(); + + ctx.beginPath(); + ctx.rect(rect.x, rect.y, rect.width, rect.height); + ctx.rect(marginRect.x, marginRect.y, marginRect.width, marginRect.height); + ctx.closePath(); + ctx.fillStyle = 'rgba(255,193,125,0.5)'; + ctx.fill('evenodd'); + + ctx.beginPath(); + ctx.arc(position.x, position.y, 5, 0, Math.PI * 2); + ctx.closePath(); + ctx.fillStyle = 'rgba(255, 255, 255, 1)'; + ctx.fill(); + + ctx.restore(); + } else { + context.beginPath(); + context.rect(rect.x, rect.y, rect.width, rect.height); + context.moveTo(position.x, position.y); + context.arc(position.x, position.y, 4, 0, Math.PI * 2, false); + context.closePath(); + context.strokeShape(this); + } } } diff --git a/src/components/LayeredLayout.ts b/src/components/LayeredLayout.ts new file mode 100644 index 00000000..332363f5 --- /dev/null +++ b/src/components/LayeredLayout.ts @@ -0,0 +1,54 @@ +import {LayoutGroup, LayoutGroupConfig} from './LayoutGroup'; +import {LayoutAttrs} from './ILayoutNode'; +import {Size} from '../types'; +import {IRect, Vector2d} from 'konva/lib/types'; +import {Group} from 'konva/lib/Group'; +import {Shape} from 'konva/lib/Shape'; +import {Container} from 'konva/lib/Container'; + +export class LayeredLayout extends LayoutGroup { + add(...children: (Group | Shape)[]): this { + super.add(...children); + this.handleLayoutChange(); + return this; + } + + protected handleLayoutChange() { + this.offset(this.getOriginOffset()); + this.fireLayoutChange(); + } + + public getLayoutSize(custom?: Partial): Size { + return this.getClientRect({skipTransform: true}); + } + + public getOriginOffset(custom?: LayoutGroupConfig): Vector2d { + const offset = super.getOriginOffset(custom); + const rect = this.getClientRect({relativeTo: this}); + + return { + x: offset.x + rect.x + rect.width / 2, + y: offset.y + rect.y + rect.height / 2, + }; + } + + getClientRect(config?: { + skipTransform?: boolean; + skipShadow?: boolean; + skipStroke?: boolean; + relativeTo?: Container; + }): IRect { + const rect = this.getPadd().expand( + super.getClientRect({ + ...config, + skipTransform: true, + }), + ); + + if (!config?.skipTransform) { + return this._transformedRect(rect, config?.relativeTo); + } + + return rect; + } +} diff --git a/src/components/Layout.ts b/src/components/Layout.ts index 95fc630f..57f1e3fe 100644 --- a/src/components/Layout.ts +++ b/src/components/Layout.ts @@ -1,11 +1,12 @@ import {Group} from 'konva/lib/Group'; -import {GetSet} from 'konva/lib/types'; -import {_registerNode} from 'konva/lib/Global'; +import {GetSet, IRect} from 'konva/lib/types'; import {Factory} from 'konva/lib/Factory'; import {Shape} from 'konva/lib/Shape'; import {LayoutGroup, LayoutGroupConfig} from './LayoutGroup'; import {LayoutShape} from './LayoutShape'; import {Center, Origin, Size} from '../types'; +import {Container} from 'konva/lib/Container'; +import {getClientRect} from "MC/components/ILayoutNode"; export interface LayoutConfig extends LayoutGroupConfig { direction?: Center; @@ -20,7 +21,7 @@ export class Layout extends LayoutGroup { } getLayoutSize(): Size { - return this.getPadd().apply({ + return this.getPadd().expand({ width: this.contentSize?.width ?? 0, height: this.contentSize?.height ?? 0, }); @@ -70,10 +71,16 @@ export class Layout extends LayoutGroup { this.fireLayoutChange(); } -} -Layout.prototype.className = 'Layout'; -_registerNode(Layout); + getClientRect(config?: { + skipTransform?: boolean; + skipShadow?: boolean; + skipStroke?: boolean; + relativeTo?: Container; + }): IRect { + return getClientRect(this, config); + } +} Factory.addGetterSetter( Layout, diff --git a/src/components/LayoutGroup.ts b/src/components/LayoutGroup.ts index 26c19c79..586d38fb 100644 --- a/src/components/LayoutGroup.ts +++ b/src/components/LayoutGroup.ts @@ -1,8 +1,7 @@ import {Group} from 'konva/lib/Group'; -import {Container, ContainerConfig} from 'konva/lib/Container'; +import {ContainerConfig} from 'konva/lib/Container'; import {Origin, Size, PossibleSpacing, Spacing} from '../types'; import { - getClientRect, getOriginDelta, getOriginOffset, ILayoutNode, @@ -11,7 +10,6 @@ import { LayoutAttrs, } from '../components/ILayoutNode'; import Konva from 'konva'; -import {IRect} from 'konva/lib/types'; import Vector2d = Konva.Vector2d; export type LayoutGroupConfig = Partial & ContainerConfig; @@ -106,15 +104,6 @@ export abstract class LayoutGroup extends Group implements ILayoutNode { ); } - public getClientRect(config?: { - skipTransform?: boolean; - skipShadow?: boolean; - skipStroke?: boolean; - relativeTo?: Container; - }): IRect { - return getClientRect(this, config); - } - protected fireLayoutChange() { this.getParent()?.fire(LAYOUT_CHANGE_EVENT, undefined, true); } diff --git a/src/components/Surface.ts b/src/components/Surface.ts index 134df2ca..6805730e 100644 --- a/src/components/Surface.ts +++ b/src/components/Surface.ts @@ -1,11 +1,12 @@ import {Group} from 'konva/lib/Group'; -import {ContainerConfig} from 'konva/lib/Container'; +import {Container, ContainerConfig} from 'konva/lib/Container'; import {Rect} from 'konva/lib/shapes/Rect'; import {Shape} from 'konva/lib/Shape'; import {parseColor} from 'mix-color'; import {LayoutGroup} from './LayoutGroup'; import {Origin, Size} from '../types'; import { + getClientRect, getOriginDelta, getOriginOffset, isLayoutNode, @@ -14,7 +15,8 @@ import { } from './ILayoutNode'; import {CanvasHelper} from '../helpers'; import {Context} from 'konva/lib/Context'; -import {tween} from "../animations"; +import {tween} from '../animations'; +import {IRect} from 'konva/lib/types'; export type LayoutData = LayoutAttrs & Size; export interface SurfaceMask { @@ -210,13 +212,11 @@ export class Surface extends LayoutGroup { color: this.child.getColor(), }; - this.child.position( - getOriginDelta( - this.getLayoutSize(), - Origin.Middle, - this.child.getOrigin(), - ), - ); + const offset = this.child.getOriginDelta(Origin.Middle); + this.child.position({ + x: -offset.x, + y: -offset.y, + }); } this.updateBackground(this.layoutData); @@ -271,8 +271,8 @@ export class Surface extends LayoutGroup { if (mask === null) return null; const size = this.getLayoutSize(custom); const position = { - x: size.width * mask.x / 2, - y: size.height * mask.y / 2, + x: (size.width * mask.x) / 2, + y: (size.height * mask.y) / 2, }; const farthestEdge = { x: Math.abs(position.x) + size.width / 2, @@ -287,4 +287,13 @@ export class Surface extends LayoutGroup { radius: distance * mask.radius, }; } + + getClientRect(config?: { + skipTransform?: boolean; + skipShadow?: boolean; + skipStroke?: boolean; + relativeTo?: Container; + }): IRect { + return getClientRect(this, config); + } } diff --git a/src/types/Spacing.ts b/src/types/Spacing.ts index 5cc5640c..5909a1df 100644 --- a/src/types/Spacing.ts +++ b/src/types/Spacing.ts @@ -1,4 +1,5 @@ import {Size} from './Size'; +import {IRect} from 'konva/lib/types'; interface ISpacing { top: number; @@ -66,10 +67,29 @@ export class Spacing implements ISpacing { return this; } - public apply(size: Size): Size { - return { - width: size.width + this.x, - height: size.height + this.y, - }; + public expand(value: T): T { + const result = {...value}; + + result.width += this.x; + result.height += this.y; + if ('x' in result) { + result.x -= this.left; + result.y -= this.top; + } + + return result; + } + + public shrink(value: T): T { + const result = {...value}; + + result.width -= this.x; + result.height -= this.y; + if ('x' in result) { + result.x += this.left; + result.y += this.top; + } + + return result; } }