Skip to content

Commit

Permalink
feat: line control handle(close #113)
Browse files Browse the repository at this point in the history
  • Loading branch information
F-star committed Dec 23, 2023
1 parent bb0f1ef commit 00347e5
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC, useContext, useEffect, useState } from 'react';
import { EditorContext } from '../../../context';
import { MutateGraphsAndRecord } from '../../../editor/service/mutate_graphs_and_record';
import { remainDecimal } from '../../../utils/common';
import { getElementRotatedXY } from '../../../utils/graphics';
import { getRectRotatedXY } from '../../../utils/geo';
import { deg2Rad, normalizeRadian, rad2Deg } from '@suika/geo';
import { BaseCard } from '../BaseCard';
import NumberInput from '../../input/NumberInput';
Expand Down Expand Up @@ -46,7 +46,7 @@ export const ElementsInfoCards: FC = () => {
}: {
x: number | typeof MIXED;
y: number | typeof MIXED;
} = getElementRotatedXY(items[0]);
} = getRectRotatedXY(items[0]);
let newGraphType: GraphType | typeof MIXED = items[0].type;
let newWidth: number | typeof MIXED = items[0].width;
let newHeight: number | typeof MIXED = items[0].height;
Expand All @@ -59,7 +59,7 @@ export const ElementsInfoCards: FC = () => {
newGraphType = MIXED;
}
const { x: currentRotatedX, y: currentRotatedY } =
getElementRotatedXY(element);
getRectRotatedXY(element);
if (!isEqual(newRotatedX, currentRotatedX)) {
newRotatedX = MIXED;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/suika/src/editor/ref_line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
isRectIntersect2,
pointsToHLines,
pointsToVLines,
} from '../utils/graphics';
} from '../utils/geo';
import { getClosestValInSortedArr } from '../utils/common';
import { drawLine, drawXShape } from '../utils/canvas';
import { arrMap, forEach } from '../utils/array_util';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ type HitTest = (
rect: IRectWithRotation,
) => boolean;

type GetCursor = (type: string, rotation: number) => ICursor;
type GetCursor = (
type: string,
rotation: number,
selectedBox: IRectWithRotation,
) => ICursor;

export class ControlHandle {
cx: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export class ControlHandleManager {
...(this.customHandlesVisible ? this.customHandles : []),
];

const box = this.editor.selectedBox.getBox();
const selectedBox = this.editor.selectedBox.getBox()!;

for (let i = handles.length - 1; i >= 0; i--) {
const handle = handles[i];
Expand All @@ -201,13 +201,18 @@ export class ControlHandleManager {
}

const isHit = handle.hitTest
? handle.hitTest(hitPointVW.x, hitPointVW.y, handle.padding, box!)
? handle.hitTest(
hitPointVW.x,
hitPointVW.y,
handle.padding,
selectedBox,
)
: handle.graph.hitTest(hitPointVW.x, hitPointVW.y, handle.padding);

if (isHit) {
return {
handleName: type,
cursor: handle.getCursor(type, rotation),
cursor: handle.getCursor(type, rotation, selectedBox),
};
}
}
Expand Down
13 changes: 11 additions & 2 deletions packages/suika/src/editor/scene/control_handle_manager/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import { ITexture, TextureType } from '../../texture';
import { Rect } from '../rect';
import { ControlHandle } from './control_handle';
import { ICursor } from '../../cursor_manager';
import { normalizeDegree, rad2Deg } from '@suika/geo';
import { IRectWithRotation, normalizeDegree, rad2Deg } from '@suika/geo';
import { ITransformHandleType } from './type';

const getResizeCursor = (type: string, rotation: number): ICursor => {
const getResizeCursor = (
type: string,
rotation: number,
selectedBox: IRectWithRotation,
): ICursor => {
if (selectedBox.height === 0) {
// be considered as a line
return 'move';
}

let dDegree = 0;
switch (type) {
case 'se':
Expand Down
28 changes: 13 additions & 15 deletions packages/suika/src/editor/scene/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ import { calcCoverScale, genId, objectNameGenerator } from '../../utils/common';
import { IRectWithRotation, isPointInRect, isRectIntersect } from '@suika/geo';
import {
getAbsoluteCoords,
getElementRotatedXY,
getRectRotatedXY,
getRectCenterPoint,
} from '../../utils/graphics';
import { normalizeRect, normalizeRadian, isRectContain } from '@suika/geo';
} from '../../utils/geo';
import { normalizeRadian, isRectContain } from '@suika/geo';
import { transformRotate } from '@suika/geo';
import { DEFAULT_IMAGE, ITexture, TextureImage } from '../texture';
import { ImgManager } from '../Img_manager';
import { HALF_PI } from '../../constant';
import { drawRoundRectPath, rotateInCanvas } from '../../utils/canvas';
import { ControlHandle } from './control_handle_manager';
import { getResizedLine } from './utils';

export interface GraphAttrs {
type?: GraphType;
Expand Down Expand Up @@ -310,29 +311,26 @@ export class Graph {
}

setRotatedX(rotatedX: number) {
const { x: prevRotatedX } = getElementRotatedXY(this);
const { x: prevRotatedX } = getRectRotatedXY(this);
this.x = this.x + rotatedX - prevRotatedX;
}
setRotatedY(rotatedY: number) {
const { y: prevRotatedY } = getElementRotatedXY(this);
const { y: prevRotatedY } = getRectRotatedXY(this);
this.y = this.y + rotatedY - prevRotatedY;
}

updateByControlHandle(
type: string, // 'se' | 'ne' | 'nw' | 'sw' | 'n' | 'e' | 's' | 'w',
newPos: IPoint,
oldBox: IBox2WithRotation,
keepRatio = false,
scaleFromCenter = false,
isShiftPressing = false,
isAltPressing = false,
) {
const rect = getResizedRect(
type,
newPos,
oldBox,
keepRatio,
scaleFromCenter,
);
this.setAttrs(normalizeRect(rect));
const rect =
this.height === 0
? getResizedLine(type, newPos, oldBox, isShiftPressing, isAltPressing)
: getResizedRect(type, newPos, oldBox, isShiftPressing, isAltPressing);
this.setAttrs(rect);
}
draw(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
107 changes: 107 additions & 0 deletions packages/suika/src/editor/scene/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { IPoint, IRect, normalizeRadian, transformRotate } from '@suika/geo';
import {
adjustSizeToKeepPolarSnap,
getRectCenterPoint,
getRectRotatedXY,
} from '../../utils/geo';
import { IBox2WithRotation } from '../../type';

/**
* Get the new position of the line when resizing
* we consider the graph with 0 height as a line
*
* TODO: reuse, this is something same code in tool_draw_graph.ts
*/
export const getResizedLine = (
/** control type, 'se' | 'ne' | 'nw' | 'sw' */
type: string,
newPos: IPoint,
oldBox: IBox2WithRotation,
/** keep rotation in 0 45 90 ... */
keepPolarSnap: boolean,
scaleFromCenter: boolean,
) => {
const isControlInLeft = type === 'nw' || type === 'sw';

const { x, y } = newPos;
let startX: number;
let startY: number;
if (isControlInLeft) {
const [cx, cy] = getRectCenterPoint(oldBox);
const rightTop = transformRotate(
oldBox.x + oldBox.width,
oldBox.y + oldBox.height,
oldBox.rotation || 0,
cx,
cy,
);
startX = rightTop.x;
startY = rightTop.y;
} else {
const leftTop = getRectRotatedXY(oldBox);
startX = leftTop.x;
startY = leftTop.y;
}

const width = x - startX;
const height = y - startY;

let rect = {
x: startX,
y: startY,
width, // width may be negative
height, // also height
};

let cx = 0;
let cy = 0;
if (scaleFromCenter) {
const [oldCx, oldCy] = getRectCenterPoint(oldBox);
const w = x - oldCx;
const h = y - oldCy;
rect = {
x: oldCx - w,
y: oldCy - h,
width: w * 2,
height: h * 2,
};

cx = rect.x + rect.width / 2;
cy = rect.y + rect.height / 2;
}

if (keepPolarSnap) {
rect = adjustSizeToKeepPolarSnap(rect);
}

if (scaleFromCenter) {
rect.x = cx - rect.width / 2;
rect.y = cy - rect.height / 2;
}

if (isControlInLeft) {
rect.width = -rect.width;
rect.height = -rect.height;
}
const attrs = getLineAttrsByRect(rect);
if (isControlInLeft) {
attrs.x -= rect.width;
attrs.y -= rect.height;
}

return attrs;
};

const getLineAttrsByRect = ({ x, y, width, height }: IRect) => {
const rotation = normalizeRadian(Math.atan2(height, width));
const cx = x + width / 2;
const cy = y + height / 2;
const p = transformRotate(x, y, -rotation, cx, cy);
width = Math.sqrt(width * width + height * height);
return {
x: p.x,
y: p.y,
width,
rotation,
};
};
2 changes: 1 addition & 1 deletion packages/suika/src/editor/selected_box.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IPoint, IRectWithRotation, isPointInRect } from '@suika/geo';
import { Editor } from './editor';
import { getRectCenterPoint } from '../utils/graphics';
import { getRectCenterPoint } from '../utils/geo';
import { rotateInCanvas } from '../utils/canvas';
import EventEmitter from '../utils/event_emitter';

Expand Down
2 changes: 1 addition & 1 deletion packages/suika/src/editor/selected_elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Graph } from './scene/graph';
import { IBox } from '../type';
import { isSameArray } from '../utils/common';
import EventEmitter from '../utils/event_emitter';
import { getRectCenterPoint } from '../utils/graphics';
import { getRectCenterPoint } from '../utils/geo';
import { getMergedRect } from '@suika/geo';
import { RemoveElement } from './commands/remove_element';
import { Editor } from './editor';
Expand Down
10 changes: 5 additions & 5 deletions packages/suika/src/editor/service/mutate_graphs_and_record.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getElementRotatedXY } from '../../utils/graphics';
import { getRectRotatedXY } from '../../utils/geo';
import { SetElementsAttrs } from '../commands/set_elements_attrs';
import { Editor } from '../editor';
import { Graph } from '../scene/graph';
Expand Down Expand Up @@ -57,9 +57,9 @@ export const MutateGraphsAndRecord = {
width: el.width,
}));
elements.forEach((el) => {
const { x: preRotatedX, y: preRotatedY } = getElementRotatedXY(el);
const { x: preRotatedX, y: preRotatedY } = getRectRotatedXY(el);
el.width = width;
const { x: rotatedX, y: rotatedY } = getElementRotatedXY(el);
const { x: rotatedX, y: rotatedY } = getRectRotatedXY(el);
const dx = rotatedX - preRotatedX;
const dy = rotatedY - preRotatedY;
el.x -= dx;
Expand All @@ -85,9 +85,9 @@ export const MutateGraphsAndRecord = {
height: el.height,
}));
elements.forEach((el) => {
const { x: preRotatedX, y: preRotatedY } = getElementRotatedXY(el);
const { x: preRotatedX, y: preRotatedY } = getRectRotatedXY(el);
el.height = height;
const { x: rotatedX, y: rotatedY } = getElementRotatedXY(el);
const { x: rotatedX, y: rotatedY } = getRectRotatedXY(el);
const dx = rotatedX - preRotatedX;
const dy = rotatedY - preRotatedY;
el.x -= dx;
Expand Down
3 changes: 2 additions & 1 deletion packages/suika/src/editor/tools/tool_draw_graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export abstract class DrawGraphTool implements ITool {
const size = Math.max(Math.abs(width), Math.abs(height));
rect.height = (Math.sign(height) || 1) * size;
rect.width = (Math.sign(width) || 1) * size;
return rect;
}

/**
Expand Down Expand Up @@ -190,7 +191,7 @@ export abstract class DrawGraphTool implements ITool {
}

if (keepSquare) {
this.adjustSizeWhenShiftPressing(rect);
rect = this.adjustSizeWhenShiftPressing(rect);
}

if (isStartPtAsCenter) {
Expand Down
27 changes: 2 additions & 25 deletions packages/suika/src/editor/tools/tool_draw_line.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { Line } from '../scene/line';
import { DrawGraphTool } from './tool_draw_graph';
import { ITool } from './type';
import { IRect } from '@suika/geo';
import { calcVectorRadian } from '../../utils/graphics';
import { adjustSizeToKeepPolarSnap } from '../../utils/geo';
import { transformRotate } from '@suika/geo';
import { HALF_PI } from '../../constant';
import { normalizeRadian } from '@suika/geo';

export class DrawLineTool extends DrawGraphTool implements ITool {
Expand Down Expand Up @@ -34,29 +33,7 @@ export class DrawLineTool extends DrawGraphTool implements ITool {
}

protected override adjustSizeWhenShiftPressing(rect: IRect) {
const radian = calcVectorRadian(
rect.x,
rect.y,
rect.x + rect.width,
rect.y + rect.height,
);

const { width, height } = rect;
const remainder = radian % HALF_PI;
if (remainder < Math.PI / 8 || remainder > (Math.PI * 3) / 8) {
if (Math.abs(width) > Math.abs(height)) {
rect.height = 0;
} else {
rect.width = 0;
}
} else {
const min = Math.min(Math.abs(width), Math.abs(height));
const max = Math.max(Math.abs(width), Math.abs(height));
const size = min + (max - min) / 2;

rect.height = (Math.sign(height) || 1) * size;
rect.width = (Math.sign(width) || 1) * size;
}
return adjustSizeToKeepPolarSnap(rect);
}

protected override updateGraph(rect: IRect) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IPoint } from '../../../type';
import { getClosestTimesVal } from '../../../utils/common';
import { calcVectorRadian } from '../../../utils/graphics';
import { calcVectorRadian } from '../../../utils/geo';
import { SetElementsAttrs } from '../../commands/set_elements_attrs';
import { Editor } from '../../editor';
import { IBaseTool } from '../type';
Expand Down
Loading

0 comments on commit 00347e5

Please sign in to comment.