Skip to content

Commit

Permalink
refactor: Clean up naming over cross and diagonal cross
Browse files Browse the repository at this point in the history
- They are same in terms of data model
  • Loading branch information
miyanokomiya committed Apr 3, 2024
1 parent 9118cf8 commit c3f2c5a
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { ShapeComposite } from "../shapeComposite";
import { applyFillStyle } from "../../utils/fillStyle";
import { TAU, getRotateFn } from "../../utils/geometry";
import { defineShapeHandler } from "./core";
import { DiagonalCrossShape } from "../../shapes/polygons/diagonalCross";
import { applyLocalSpace, renderValueLabel } from "../../utils/renderer";
import { applyStrokeStyle } from "../../utils/strokeStyle";
import { CrossShape } from "../../shapes/polygons/cross";

const ANCHOR_SIZE = 6;

type AnchorType = "crossSize";

interface DiagonalCrossHitResult {
interface CrossHitResult {
type: AnchorType;
}

Expand All @@ -21,9 +21,9 @@ interface Option {
targetId: string;
}

export const newDiagonalCrossHandler = defineShapeHandler<DiagonalCrossHitResult, Option>((option) => {
export const newCrossHandler = defineShapeHandler<CrossHitResult, Option>((option) => {
const shapeComposite = option.getShapeComposite();
const shape = shapeComposite.shapeMap[option.targetId] as DiagonalCrossShape;
const shape = shapeComposite.shapeMap[option.targetId] as CrossShape;
const shapeRect = { x: shape.p.x, y: shape.p.y, width: shape.width, height: shape.height };
const rotateFn = getRotateFn(shape.rotation, getRectCenter(shapeRect));

Expand All @@ -32,7 +32,7 @@ export const newDiagonalCrossHandler = defineShapeHandler<DiagonalCrossHitResult
return { controlSizeP };
}

function hitTest(p: IVec2, scale = 1): DiagonalCrossHitResult | undefined {
function hitTest(p: IVec2, scale = 1): CrossHitResult | undefined {
const threshold = ANCHOR_SIZE * scale;
const { controlSizeP } = getAnchors();
const adjustedP = sub(rotateFn(p, true), shape.p);
Expand All @@ -42,12 +42,7 @@ export const newDiagonalCrossHandler = defineShapeHandler<DiagonalCrossHitResult
}
}

function render(
ctx: CanvasRenderingContext2D,
style: StyleScheme,
scale: number,
hitResult?: DiagonalCrossHitResult,
) {
function render(ctx: CanvasRenderingContext2D, style: StyleScheme, scale: number, hitResult?: CrossHitResult) {
const threshold = ANCHOR_SIZE * scale;
const { controlSizeP } = getAnchors();
applyLocalSpace(ctx, shapeRect, shape.rotation, () => {
Expand Down Expand Up @@ -77,11 +72,11 @@ export const newDiagonalCrossHandler = defineShapeHandler<DiagonalCrossHitResult
};
});

export function renderMovingDiagonalCrossAnchor(
export function renderMovingCrossAnchor(
ctx: CanvasRenderingContext2D,
style: StyleScheme,
scale: number,
shape: DiagonalCrossShape,
shape: CrossShape,
showLabel = false,
) {
const threshold = ANCHOR_SIZE * scale;
Expand Down
149 changes: 149 additions & 0 deletions src/composables/states/appCanvas/cross/crossSelectedState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
handleCommonPointerDownLeftOnSingleSelection,
handleCommonPointerDownRightOnSingleSelection,
handleIntransientEvent,
} from "../commons";
import { newSelectionHubState } from "../selectionHubState";
import { CONTEXT_MENU_SHAPE_SELECTED_ITEMS } from "../contextMenuItems";
import { BoundingBox, newBoundingBox } from "../../../boundingBox";
import { newResizingState } from "../resizingState";
import { newRotatingState } from "../rotatingState";
import { CrossShape } from "../../../../shapes/polygons/cross";
import { newCrossHandler, renderMovingCrossAnchor } from "../../../shapeHandlers/crossHandler";
import { defineIntransientState } from "../intransientState";
import { newPointerDownEmptyState } from "../pointerDownEmptyState";
import { movingShapeControlState } from "../movingShapeControlState";
import { getShapeDetransform, getShapeTransform } from "../../../../shapes/simplePolygon";
import { applyAffine, clamp } from "okageo";
import { snapNumber } from "../../../../utils/geometry";

export const newCrossSelectedState = defineIntransientState(() => {
let targetShape: CrossShape;
let shapeHandler: ReturnType<typeof newCrossHandler>;
let boundingBox: BoundingBox;

return {
getLabel: () => "CrossSelected",
onStart: (ctx) => {
ctx.showFloatMenu();
ctx.setCommandExams([]);
targetShape = ctx.getShapeComposite().shapeMap[ctx.getLastSelectedShapeId() ?? ""] as CrossShape;
shapeHandler = newCrossHandler({ getShapeComposite: ctx.getShapeComposite, targetId: targetShape.id });

const shapeComposite = ctx.getShapeComposite();
boundingBox = newBoundingBox({
path: shapeComposite.getLocalRectPolygon(targetShape),
});
},
onEnd: (ctx) => {
ctx.hideFloatMenu();
ctx.setCommandExams();
ctx.setContextMenuList();
ctx.setCursor();
},
handleEvent: (ctx, event) => {
if (!targetShape) return newSelectionHubState;

switch (event.type) {
case "pointerdown":
ctx.setContextMenuList();

switch (event.data.options.button) {
case 0: {
const hitResult = shapeHandler.hitTest(event.data.point, ctx.getScale());
shapeHandler.saveHitResult(hitResult);
if (hitResult) {
switch (hitResult.type) {
case "crossSize":
return () => {
let showLabel = false;
return movingShapeControlState<CrossShape>({
targetId: targetShape.id,
snapType: "custom",
patchFn: (s, p, movement) => {
const localP = applyAffine(getShapeDetransform(s), p);
let nextSize = clamp(1, Math.min(s.width / 2, s.height / 2), localP.x - s.width / 2) * 2;
if (movement.ctrl) {
showLabel = false;
} else {
nextSize = snapNumber(nextSize, 1);
showLabel = true;
}
return { crossSize: nextSize };
},
getControlFn: (s) =>
applyAffine(getShapeTransform(s), { x: s.width / 2 + s.crossSize / 2, y: s.height / 2 }),
renderFn: (ctx, renderCtx, s) => {
renderMovingCrossAnchor(renderCtx, ctx.getStyleScheme(), ctx.getScale(), s, showLabel);
},
});
};
default:
return;
}
}

const boundingHitResult = boundingBox.hitTest(event.data.point, ctx.getScale());
if (boundingHitResult) {
switch (boundingHitResult.type) {
case "corner":
case "segment":
return () => newResizingState({ boundingBox, hitResult: boundingHitResult });
case "rotation":
return () => newRotatingState({ boundingBox });
}
}

return handleCommonPointerDownLeftOnSingleSelection(
ctx,
event,
targetShape.id,
ctx.getShapeComposite().getSelectionScope(targetShape),
);
}
case 1:
return () => newPointerDownEmptyState(event.data.options);
case 2: {
return handleCommonPointerDownRightOnSingleSelection(
ctx,
event,
targetShape.id,
ctx.getShapeComposite().getSelectionScope(targetShape),
);
}
default:
return;
}
case "pointerhover": {
const nextHitResult = shapeHandler.hitTest(event.data.current, ctx.getScale());
if (shapeHandler.saveHitResult(nextHitResult)) {
ctx.redraw();
}

if (nextHitResult) {
boundingBox.saveHitResult();
return;
}

const hitBounding = boundingBox.hitTest(event.data.current, ctx.getScale());
if (boundingBox.saveHitResult(hitBounding)) {
ctx.redraw();
}
break;
}
case "contextmenu":
ctx.setContextMenuList({
items: CONTEXT_MENU_SHAPE_SELECTED_ITEMS,
point: event.data.point,
});
return;
default:
return handleIntransientEvent(ctx, event);
}
},
render: (ctx, renderCtx) => {
boundingBox.render(renderCtx, ctx.getStyleScheme(), ctx.getScale());
shapeHandler.render(renderCtx, ctx.getStyleScheme(), ctx.getScale());
},
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BoundingBox, newBoundingBox } from "../../../boundingBox";
import { newResizingState } from "../resizingState";
import { newRotatingState } from "../rotatingState";
import { DiagonalCrossShape } from "../../../../shapes/polygons/diagonalCross";
import { newDiagonalCrossHandler, renderMovingDiagonalCrossAnchor } from "../../../shapeHandlers/diagonalCrossHandler";
import { newCrossHandler, renderMovingCrossAnchor } from "../../../shapeHandlers/crossHandler";
import { defineIntransientState } from "../intransientState";
import { newPointerDownEmptyState } from "../pointerDownEmptyState";
import { movingShapeControlState } from "../movingShapeControlState";
Expand All @@ -19,7 +19,7 @@ import { snapNumber } from "../../../../utils/geometry";

export const newDiagonalCrossSelectedState = defineIntransientState(() => {
let targetShape: DiagonalCrossShape;
let shapeHandler: ReturnType<typeof newDiagonalCrossHandler>;
let shapeHandler: ReturnType<typeof newCrossHandler>;
let boundingBox: BoundingBox;

return {
Expand All @@ -28,7 +28,7 @@ export const newDiagonalCrossSelectedState = defineIntransientState(() => {
ctx.showFloatMenu();
ctx.setCommandExams([]);
targetShape = ctx.getShapeComposite().shapeMap[ctx.getLastSelectedShapeId() ?? ""] as DiagonalCrossShape;
shapeHandler = newDiagonalCrossHandler({ getShapeComposite: ctx.getShapeComposite, targetId: targetShape.id });
shapeHandler = newCrossHandler({ getShapeComposite: ctx.getShapeComposite, targetId: targetShape.id });

const shapeComposite = ctx.getShapeComposite();
boundingBox = newBoundingBox({
Expand Down Expand Up @@ -74,13 +74,7 @@ export const newDiagonalCrossSelectedState = defineIntransientState(() => {
getControlFn: (s) =>
applyAffine(getShapeTransform(s), { x: s.width / 2 + s.crossSize / 2, y: s.height / 2 }),
renderFn: (ctx, renderCtx, s) => {
renderMovingDiagonalCrossAnchor(
renderCtx,
ctx.getStyleScheme(),
ctx.getScale(),
s,
showLabel,
);
renderMovingCrossAnchor(renderCtx, ctx.getStyleScheme(), ctx.getScale(), s, showLabel);
},
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { AppCanvasState } from "../core";
import { newSelectionHubState } from "../selectionHubState";
import { getRotateFn } from "../../../../utils/geometry";
import { IVec2, add, clamp, sub } from "okageo";
import { getPatchByLayouts } from "../../../shapeLayoutHandler";
import { DiagonalCrossShape } from "../../../../shapes/polygons/diagonalCross";
import { renderMovingCrossAnchor } from "../../../shapeHandlers/crossHandler";
import { renderValueLabel } from "../../../../utils/renderer";
import { COMMAND_EXAM_SRC } from "../commandExams";

interface Option {
targetId: string;
}

export function newTransformingDiagonalCrossState(option: Option): AppCanvasState {
let targetShape: DiagonalCrossShape;
let srcControlP: IVec2;
let rotateFn: ReturnType<typeof getRotateFn>;
let snappedAngle: number | undefined;

return {
getLabel: () => "TransformingDiagonalCross",
onStart: (ctx) => {
targetShape = ctx.getShapeComposite().shapeMap[option.targetId] as DiagonalCrossShape;
if (!targetShape) return newSelectionHubState;

rotateFn = getRotateFn(targetShape.rotation, {
x: targetShape.p.x + targetShape.width / 2,
y: targetShape.p.y + targetShape.height / 2,
});
srcControlP = rotateFn({
x: targetShape.p.x + targetShape.width / 2 + targetShape.crossSize / 2,
y: targetShape.p.y,
});

ctx.setCommandExams([COMMAND_EXAM_SRC.DISABLE_SNAP]);
ctx.startDragging();
},
onEnd: (ctx) => {
ctx.setTmpShapeMap({});
ctx.setCommandExams();
ctx.stopDragging();
},
handleEvent: (ctx, event) => {
switch (event.type) {
case "pointermove": {
if (targetShape.width === 0) return;

const diff = sub(event.data.current, event.data.start);

const { width, height } = targetShape;
const nextControlP = rotateFn(add(diff, srcControlP), true);
const nextSize =
clamp(1, Math.min(width / 2, height / 2), nextControlP.x - (targetShape.p.x + width / 2)) * 2;

const shapeComposite = ctx.getShapeComposite();
const patch = { crossSize: nextSize } as Partial<DiagonalCrossShape>;
const layoutPatch = getPatchByLayouts(shapeComposite, {
update: { [targetShape.id]: patch },
});
ctx.setTmpShapeMap(layoutPatch);
return;
}
case "pointerup": {
ctx.patchShapes(ctx.getTmpShapeMap());
return newSelectionHubState;
}
case "shape-updated": {
if (event.data.keys.has(targetShape.id)) return newSelectionHubState;
return;
}
default:
return;
}
},
render: (ctx, renderCtx) => {
const tmpShape: DiagonalCrossShape = { ...targetShape, ...ctx.getTmpShapeMap()[targetShape.id] };
renderMovingCrossAnchor(renderCtx, ctx.getStyleScheme(), ctx.getScale(), tmpShape);

if (snappedAngle !== undefined) {
renderValueLabel(
renderCtx,
snappedAngle,
rotateFn({ x: targetShape.p.x + targetShape.width / 2, y: targetShape.p.y + targetShape.height / 2 }),
0,
ctx.getScale(),
);
}
},
};
}
7 changes: 3 additions & 4 deletions src/shapes/polygons/diagonalCross.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ShapeStruct, createBaseShape } from "../core";
import { SimplePath, SimplePolygonShape, getStructForSimplePolygon } from "../simplePolygon";
import { SimplePath, getStructForSimplePolygon } from "../simplePolygon";
import { createFillStyle } from "../../utils/fillStyle";
import { createStrokeStyle } from "../../utils/strokeStyle";
import { getQuarticRoots } from "minimatrix-polyroots";
import { CrossShape } from "./cross";

export type DiagonalCrossShape = SimplePolygonShape & {
crossSize: number;
};
export type DiagonalCrossShape = CrossShape;

export const struct: ShapeStruct<DiagonalCrossShape> = {
...getStructForSimplePolygon<DiagonalCrossShape>(getDiagonalCrossPath),
Expand Down

0 comments on commit c3f2c5a

Please sign in to comment.