Skip to content

Commit

Permalink
fix: Take care of group shape on box align
Browse files Browse the repository at this point in the history
- Their positions aren't reliable
  • Loading branch information
miyanokomiya committed Oct 26, 2023
1 parent 4806c36 commit 6994a7b
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 4 deletions.
24 changes: 23 additions & 1 deletion src/composables/alignHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AlignBoxShape } from "../shapes/align/alignBox";
import { RectangleShape } from "../shapes/rectangle";
import { getNextShapeComposite, newShapeComposite } from "./shapeComposite";
import { EntityPatchInfo, Shape } from "../models";
import { TextShape } from "../shapes/text";

const box0 = createShape<AlignBoxShape>(getCommonStruct, "align_box", {
id: "box0",
Expand Down Expand Up @@ -183,6 +184,26 @@ describe("getNextAlignLayout", () => {
expect(result[group0.id]).toEqual({ p: { x: 0, y: 40 } });
expect(result[child0.id]).toEqual({ p: { x: 0, y: 40 } });
});

test("should take care of group shape's position", () => {
const group0 = createShape(getCommonStruct, "group", {
id: "group0",
parentId: box0.id,
});
const child0 = createShape<RectangleShape>(getCommonStruct, "rectangle", {
id: "child0",
parentId: group0.id,
width: 10,
height: 20,
});
const shapeComposite = newShapeComposite({
shapes: [box0, rect0, rect1, group0, child0],
getStruct: getCommonStruct,
});
const result = getNextAlignLayout(shapeComposite, box0.id);
expect(result).toHaveProperty(group0.id);
expect(result[child0.id]).toEqual({ p: { x: 0, y: 80 } });
});
});

describe("getModifiedAlignRootIds", () => {
Expand Down Expand Up @@ -269,9 +290,10 @@ describe("canAttendToAlignBox", () => {
const line0 = createShape(getCommonStruct, "line", {
id: "line0",
});
const label0 = createShape(getCommonStruct, "text", {
const label0 = createShape<TextShape>(getCommonStruct, "text", {
id: "label0",
parentId: line0.id,
lineAttached: 0.5,
});
const group0 = createShape(getCommonStruct, "group", {
id: "group0",
Expand Down
9 changes: 6 additions & 3 deletions src/composables/alignHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { COLORS } from "../utils/color";
import { getPaddingRect } from "../utils/boxPadding";
import { isLineShape } from "../shapes/line";
import { isGroupShape } from "../shapes/group";
import { isLineLabelShape } from "../shapes/text";

export type AlignHitResult = {
seg: ISegment;
Expand Down Expand Up @@ -745,13 +746,14 @@ export function getNextAlignLayout(shapeComposite: ShapeComposite, rootId: strin
const ret: { [id: string]: Partial<Shape> & Partial<AlignBoxShape> } = {};
result.forEach((r) => {
const s = shapeComposite.shapeMap[r.id];
const srcPosition = shapeComposite.getShapeActualPosition(s);
const rotatedPatch = rotatedPatchMap[r.id] ?? {};

const patch: Partial<Shape> & Partial<AlignBoxShape> = {};
let updated = false;

const p = rotatedPatch.p ?? { x: r.rect.x, y: r.rect.y };
if (!isSame(s.p, p)) {
if (!isSame(srcPosition, p)) {
patch.p = p;
updated = true;
}
Expand All @@ -774,9 +776,9 @@ export function getNextAlignLayout(shapeComposite: ShapeComposite, rootId: strin
updated = true;
}
} else {
if (!isSame(s.p, p)) {
if (!isSame(srcPosition, p)) {
// Need to deal with all children if the shape isn't align box.
const v = sub(p, s.p);
const v = sub(p, srcPosition);
shapeComposite.getAllTransformTargets([s.id]).forEach((target) => {
if (target.id !== s.id) {
ret[target.id] = { p: add(target.p, v) };
Expand Down Expand Up @@ -880,6 +882,7 @@ export function getModifiedAlignRootIds(

export function canAttendToAlignBox(shapeComposite: ShapeComposite, shape: Shape): boolean {
if (isLineShape(shape)) return false;
if (isLineLabelShape(shape)) return false;
return (
!shape.parentId ||
!shapeComposite.shapeMap[shape.parentId] ||
Expand Down
5 changes: 5 additions & 0 deletions src/composables/shapeComposite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ export function newShapeComposite(option: Option) {
);
}

function getShapeActualPosition(shape: Shape): IVec2 {
return option.getStruct(shape.type).getActualPosition?.(shape, mergedShapeContext) ?? shape.p;
}

return {
getShapeStruct: option.getStruct,
shapes: option.shapes,
Expand All @@ -163,6 +167,7 @@ export function newShapeComposite(option: Option) {
shouldDelete,
getSelectionScope,
getMergedShapesInSelectionScope,
getShapeActualPosition,
};
}
export type ShapeComposite = ReturnType<typeof newShapeComposite>;
Expand Down
2 changes: 2 additions & 0 deletions src/composables/states/appCanvas/movingShapeLayoutHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export function handlePointerMoveOnLayout(
movingIds: string[],
option?: { boundingBox?: BoundingBox },
): TransitionValue<AppCanvasStateContext> {
if (movingIds.length === 0) return;

const shapeComposite = ctx.getShapeComposite();
if (canAlign(ctx)) {
const scope = shapeComposite.getSelectionScope(shapeComposite.shapeMap[movingIds[0]]);
Expand Down
5 changes: 5 additions & 0 deletions src/shapes/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ export interface ShapeStruct<T extends Shape> {
* When this method is undefined, valid "parentId" should be used.
*/
getSelectionScope?: (shape: T, shapeContext: ShapeContext) => ShapeSelectionScope;
/**
* Define when a shape has special position behavior.
* e.g. group shape doesn't have own position but it's derived from children.
*/
getActualPosition?: (shape: T, shapeContext: ShapeContext) => IVec2;
canAttachSmartBranch?: boolean;
shouldKeepAspect?: boolean;
/**
Expand Down
26 changes: 26 additions & 0 deletions src/shapes/group.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,30 @@ describe("struct", () => {
expect(shapeComposite.findShapeAt({ x: -0, y: -0 })).toEqual(undefined);
});
});

describe("getActualPosition", () => {
test("should return top left of the wrapper rectangle derived from children", () => {
const group = struct.create({ id: "group" });
const child0 = rectangleStruct.create({
id: "child0",
parentId: group.id,
p: { x: 1, y: 2 },
width: 3,
height: 4,
});
const child1 = rectangleStruct.create({
id: "child1",
parentId: group.id,
p: { x: 1, y: 2 },
width: 3,
height: 4,
});
expect(
newShapeComposite({
shapes: [group, child0, child1],
getStruct: getCommonStruct,
}).getShapeActualPosition(group),
).toEqual({ x: 1, y: 2 });
});
});
});
4 changes: 4 additions & 0 deletions src/shapes/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const struct: ShapeStruct<GroupShape> = {
getSnappingLines() {
return { v: [], h: [] };
},
getActualPosition(shape, shapeContext) {
const rect = struct.getWrapperRect(shape, shapeContext);
return { x: rect.x, y: rect.y };
},
};

export function isGroupShape(shape: Shape): shape is GroupShape {
Expand Down

0 comments on commit 6994a7b

Please sign in to comment.