mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: add reference lines for middleware select
This commit is contained in:
parent
3befb12deb
commit
66843c1568
36 changed files with 1152 additions and 205 deletions
|
|
@ -13,7 +13,8 @@ import type {
|
|||
ViewSizeInfo,
|
||||
PointSize,
|
||||
BoardExtendEventMap,
|
||||
UtilEventEmitter
|
||||
UtilEventEmitter,
|
||||
ModifyOptions
|
||||
} from '@idraw/types';
|
||||
import { Calculator } from './lib/calculator';
|
||||
import { BoardWatcher } from './lib/watcher';
|
||||
|
|
@ -288,16 +289,41 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
return this.#renderer;
|
||||
}
|
||||
|
||||
setData(data: Data): { viewSizeInfo: ViewSizeInfo } {
|
||||
setData(
|
||||
data: Data,
|
||||
opts?: {
|
||||
modifiedOptions?: ModifyOptions; // TODO
|
||||
}
|
||||
): { viewSizeInfo: ViewSizeInfo } {
|
||||
const { modifiedOptions } = opts || {};
|
||||
const sharer = this.#sharer;
|
||||
this.#sharer.setActiveStorage('data', data);
|
||||
const viewSizeInfo = sharer.getActiveViewSizeInfo();
|
||||
const viewScaleInfo = sharer.getActiveViewScaleInfo();
|
||||
// const currentScaleInfo = sharer.getActiveViewScaleInfo();
|
||||
const newViewContextSize = calcElementsContextSize(data.elements, {
|
||||
viewWidth: viewSizeInfo.width,
|
||||
viewHeight: viewSizeInfo.height,
|
||||
extend: true
|
||||
});
|
||||
if (modifiedOptions) {
|
||||
// TODO
|
||||
// this.#viewer.modifyViewVisibleInfoMap(data, {
|
||||
// viewSizeInfo,
|
||||
// viewScaleInfo,
|
||||
// modifyOptions: modifiedOptions
|
||||
// });
|
||||
this.#viewer.resetViewVisibleInfoMap(data, {
|
||||
viewSizeInfo,
|
||||
viewScaleInfo
|
||||
});
|
||||
} else {
|
||||
this.#viewer.resetViewVisibleInfoMap(data, {
|
||||
viewSizeInfo,
|
||||
viewScaleInfo
|
||||
});
|
||||
}
|
||||
|
||||
this.#viewer.drawFrame();
|
||||
const newViewSizeInfo = {
|
||||
...viewSizeInfo,
|
||||
|
|
@ -352,22 +378,30 @@ export class Board<T extends BoardExtendEventMap = BoardExtendEventMap> {
|
|||
}
|
||||
}
|
||||
|
||||
scale(opts: { scale: number; point: PointSize }) {
|
||||
scale(opts: { scale: number; point: PointSize; ignoreUpdateVisibleStatus?: boolean }) {
|
||||
const viewer = this.#viewer;
|
||||
const { moveX, moveY } = viewer.scale(opts);
|
||||
viewer.scroll({ moveX, moveY });
|
||||
const { ignoreUpdateVisibleStatus } = opts;
|
||||
const { moveX, moveY } = viewer.scale({
|
||||
...opts,
|
||||
...{
|
||||
ignoreUpdateVisibleStatus: true
|
||||
}
|
||||
});
|
||||
viewer.scroll({ moveX, moveY, ignoreUpdateVisibleStatus });
|
||||
}
|
||||
|
||||
scroll(opts: { moveX: number; moveY: number }) {
|
||||
return this.#viewer.scroll(opts);
|
||||
scroll(opts: { moveX: number; moveY: number; ignoreUpdateVisibleStatus?: boolean }) {
|
||||
const result = this.#viewer.scroll(opts);
|
||||
return result;
|
||||
}
|
||||
|
||||
updateViewScaleInfo(opts: { scale: number; offsetX: number; offsetY: number }) {
|
||||
return this.#viewer.updateViewScaleInfo(opts);
|
||||
const result = this.#viewer.updateViewScaleInfo(opts);
|
||||
return result;
|
||||
}
|
||||
|
||||
resize(newViewSize: ViewSizeInfo) {
|
||||
const viewSize = this.#viewer.resize(newViewSize);
|
||||
resize(newViewSize: ViewSizeInfo, opts?: { ignoreUpdateVisibleStatus?: boolean }) {
|
||||
const viewSize = this.#viewer.resize(newViewSize, opts);
|
||||
const { width, height, devicePixelRatio } = newViewSize;
|
||||
const { boardContent } = this.#opts;
|
||||
boardContent.viewContext.$resize({ width, height, devicePixelRatio });
|
||||
|
|
|
|||
|
|
@ -1,21 +1,58 @@
|
|||
import type { Point, Data, Element, ElementType, ViewCalculator, ViewCalculatorOptions, ViewScaleInfo, ElementSize, ViewSizeInfo } from '@idraw/types';
|
||||
import { calcViewElementSize, isViewPointInElement, getViewPointAtElement, isElementInView } from '@idraw/util';
|
||||
import type {
|
||||
Point,
|
||||
Data,
|
||||
Element,
|
||||
ElementType,
|
||||
ViewCalculator,
|
||||
ViewCalculatorOptions,
|
||||
ViewScaleInfo,
|
||||
ElementSize,
|
||||
ViewSizeInfo,
|
||||
ViewCalculatorStorage,
|
||||
ViewRectInfo,
|
||||
ModifyOptions,
|
||||
ViewVisibleInfo
|
||||
} from '@idraw/types';
|
||||
import {
|
||||
is,
|
||||
isViewPointInElement,
|
||||
getViewPointAtElement,
|
||||
isElementInView,
|
||||
Store,
|
||||
sortElementsViewVisiableInfoMap,
|
||||
updateViewVisibleInfoMapStatus,
|
||||
calcViewPointSize,
|
||||
findElementFromListByPosition,
|
||||
getGroupQueueByElementPosition,
|
||||
calcElementOriginRectInfo,
|
||||
originRectInfoToRangeRectInfo
|
||||
} from '@idraw/util';
|
||||
|
||||
export class Calculator implements ViewCalculator {
|
||||
#opts: ViewCalculatorOptions;
|
||||
#store: Store<ViewCalculatorStorage>;
|
||||
|
||||
constructor(opts: ViewCalculatorOptions) {
|
||||
this.#opts = opts;
|
||||
this.#store = new Store<ViewCalculatorStorage>({
|
||||
defaultStorage: {
|
||||
viewVisibleInfoMap: {},
|
||||
visibleCount: 0,
|
||||
invisibleCount: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toGridNum(num: number): number {
|
||||
// TODO
|
||||
// const gridUnitSize = 1; // px;
|
||||
return Math.round(num);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.#opts = null as any;
|
||||
}
|
||||
|
||||
elementSize(size: ElementSize, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ElementSize {
|
||||
return calcViewElementSize(size, { viewScaleInfo, viewSizeInfo });
|
||||
}
|
||||
|
||||
isElementInView(elem: ElementSize, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean {
|
||||
return isElementInView(elem, { viewScaleInfo, viewSizeInfo });
|
||||
}
|
||||
|
|
@ -37,4 +74,135 @@ export class Calculator implements ViewCalculator {
|
|||
const context2d = this.#opts.viewContext;
|
||||
return getViewPointAtElement(p, { ...opts, ...{ context2d } });
|
||||
}
|
||||
|
||||
resetViewVisibleInfoMap(
|
||||
data: Data,
|
||||
opts: {
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): void {
|
||||
if (data) {
|
||||
const { viewVisibleInfoMap, invisibleCount, visibleCount } = sortElementsViewVisiableInfoMap(data.elements, opts);
|
||||
this.#store.set('viewVisibleInfoMap', viewVisibleInfoMap);
|
||||
this.#store.set('invisibleCount', invisibleCount);
|
||||
this.#store.set('visibleCount', visibleCount);
|
||||
}
|
||||
}
|
||||
|
||||
updateVisiableStatus(opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }) {
|
||||
const { viewVisibleInfoMap, invisibleCount, visibleCount } = updateViewVisibleInfoMapStatus(this.#store.get('viewVisibleInfoMap'), opts);
|
||||
this.#store.set('viewVisibleInfoMap', viewVisibleInfoMap);
|
||||
this.#store.set('invisibleCount', invisibleCount);
|
||||
this.#store.set('visibleCount', visibleCount);
|
||||
}
|
||||
|
||||
calcViewRectInfoFromOrigin(
|
||||
uuid: string,
|
||||
opts: {
|
||||
checkVisible?: boolean;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): ViewRectInfo | null {
|
||||
const infoData = this.#store.get('viewVisibleInfoMap')[uuid];
|
||||
if (!infoData?.originRectInfo) {
|
||||
return null;
|
||||
}
|
||||
const { checkVisible, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { center, left, right, bottom, top, topLeft, topRight, bottomLeft, bottomRight } = infoData.originRectInfo;
|
||||
if (checkVisible === true && infoData.isVisibleInView === false) {
|
||||
return null;
|
||||
}
|
||||
const calcOpts = { viewScaleInfo, viewSizeInfo };
|
||||
|
||||
const viewRectInfo: ViewRectInfo = {
|
||||
center: calcViewPointSize(center, calcOpts),
|
||||
left: calcViewPointSize(left, calcOpts),
|
||||
right: calcViewPointSize(right, calcOpts),
|
||||
bottom: calcViewPointSize(bottom, calcOpts),
|
||||
top: calcViewPointSize(top, calcOpts),
|
||||
topLeft: calcViewPointSize(topLeft, calcOpts),
|
||||
topRight: calcViewPointSize(topRight, calcOpts),
|
||||
bottomLeft: calcViewPointSize(bottomLeft, calcOpts),
|
||||
bottomRight: calcViewPointSize(bottomRight, calcOpts)
|
||||
};
|
||||
|
||||
return viewRectInfo;
|
||||
}
|
||||
|
||||
calcViewRectInfoFromRange(
|
||||
uuid: string,
|
||||
opts: {
|
||||
checkVisible?: boolean;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): ViewRectInfo | null {
|
||||
const infoData = this.#store.get('viewVisibleInfoMap')[uuid];
|
||||
if (!infoData?.originRectInfo) {
|
||||
return null;
|
||||
}
|
||||
const { checkVisible, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { center, left, right, bottom, top, topLeft, topRight, bottomLeft, bottomRight } = infoData.rangeRectInfo;
|
||||
if (checkVisible === true && infoData.isVisibleInView === false) {
|
||||
return null;
|
||||
}
|
||||
const calcOpts = { viewScaleInfo, viewSizeInfo };
|
||||
|
||||
const viewRectInfo: ViewRectInfo = {
|
||||
center: calcViewPointSize(center, calcOpts),
|
||||
left: calcViewPointSize(left, calcOpts),
|
||||
right: calcViewPointSize(right, calcOpts),
|
||||
bottom: calcViewPointSize(bottom, calcOpts),
|
||||
top: calcViewPointSize(top, calcOpts),
|
||||
topLeft: calcViewPointSize(topLeft, calcOpts),
|
||||
topRight: calcViewPointSize(topRight, calcOpts),
|
||||
bottomLeft: calcViewPointSize(bottomLeft, calcOpts),
|
||||
bottomRight: calcViewPointSize(bottomRight, calcOpts)
|
||||
};
|
||||
|
||||
return viewRectInfo;
|
||||
}
|
||||
|
||||
modifyViewVisibleInfoMap(
|
||||
data: Data,
|
||||
opts: {
|
||||
modifyOptions: ModifyOptions; // TODO
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): void {
|
||||
const { modifyOptions, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { type, content } = modifyOptions;
|
||||
const list = data.elements;
|
||||
const viewVisibleInfoMap = this.#store.get('viewVisibleInfoMap');
|
||||
if (type === 'deleteElement') {
|
||||
const { element } = content as ModifyOptions<'deleteElement'>['content'];
|
||||
delete viewVisibleInfoMap[element.uuid];
|
||||
} else if (type === 'addElement' || type === 'updateElement') {
|
||||
const { position } = content as ModifyOptions<'addElement'>['content'];
|
||||
const element = findElementFromListByPosition(position, data.elements);
|
||||
const groupQueue = getGroupQueueByElementPosition(list, position);
|
||||
if (element) {
|
||||
const originRectInfo = calcElementOriginRectInfo(element, {
|
||||
groupQueue: groupQueue || []
|
||||
});
|
||||
const newViewVisibleInfo: ViewVisibleInfo = {
|
||||
originRectInfo,
|
||||
rangeRectInfo: is.angle(element.angle) ? originRectInfoToRangeRectInfo(originRectInfo) : originRectInfo,
|
||||
isVisibleInView: true,
|
||||
isGroup: element?.type === 'group',
|
||||
position: [...position]
|
||||
};
|
||||
viewVisibleInfoMap[element.uuid] = newViewVisibleInfo;
|
||||
if (type === 'updateElement') {
|
||||
this.updateVisiableStatus({ viewScaleInfo, viewSizeInfo });
|
||||
}
|
||||
}
|
||||
} else if (type === 'moveElement') {
|
||||
this.resetViewVisibleInfoMap(data, { viewScaleInfo, viewSizeInfo });
|
||||
}
|
||||
this.#store.set('viewVisibleInfoMap', viewVisibleInfoMap);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ export class Sharer implements StoreSharer<Record<string | number | symbol, any>
|
|||
return this.#activeStore.set(key, storage);
|
||||
}
|
||||
|
||||
getActiveStoreSnapshot(): ActiveStore {
|
||||
return this.#activeStore.getSnapshot();
|
||||
getActiveStoreSnapshot(opts?: { deepClone?: boolean }): ActiveStore {
|
||||
return this.#activeStore.getSnapshot(opts);
|
||||
}
|
||||
|
||||
getSharedStorage(key: string | number | symbol): any {
|
||||
|
|
@ -52,8 +52,8 @@ export class Sharer implements StoreSharer<Record<string | number | symbol, any>
|
|||
return this.#sharedStore.set(key, storage);
|
||||
}
|
||||
|
||||
getSharedStoreSnapshot(): Record<string, any> {
|
||||
return this.#sharedStore.getSnapshot();
|
||||
getSharedStoreSnapshot(opts?: { deepClone?: boolean }): Record<string, any> {
|
||||
return this.#sharedStore.getSnapshot(opts);
|
||||
}
|
||||
|
||||
// get/set active info
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import type {
|
|||
BoardViewer,
|
||||
BoardViewerEventMap,
|
||||
BoardViewerOptions,
|
||||
// BoardViewerStorage,
|
||||
ActiveStore,
|
||||
BoardViewerFrameSnapshot,
|
||||
ViewScaleInfo,
|
||||
ViewSizeInfo
|
||||
ViewSizeInfo,
|
||||
Data
|
||||
} from '@idraw/types';
|
||||
|
||||
const { requestAnimationFrame } = window;
|
||||
|
|
@ -84,6 +86,18 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
}
|
||||
}
|
||||
|
||||
resetViewVisibleInfoMap(
|
||||
data: Data,
|
||||
opts: {
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): void {
|
||||
if (data) {
|
||||
this.#opts.calculator.resetViewVisibleInfoMap(data, opts);
|
||||
}
|
||||
}
|
||||
|
||||
drawFrame(): void {
|
||||
const { sharer } = this.#opts;
|
||||
const activeStore: ActiveStore = sharer.getActiveStoreSnapshot();
|
||||
|
|
@ -95,8 +109,8 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
this.#drawAnimationFrame();
|
||||
}
|
||||
|
||||
scale(opts: { scale: number; point: PointSize }): { moveX: number; moveY: number } {
|
||||
const { scale, point } = opts;
|
||||
scale(opts: { scale: number; point: PointSize; ignoreUpdateVisibleStatus?: boolean }): { moveX: number; moveY: number } {
|
||||
const { scale, point, ignoreUpdateVisibleStatus } = opts;
|
||||
const { sharer } = this.#opts;
|
||||
const { moveX, moveY } = viewScale({
|
||||
scale,
|
||||
|
|
@ -105,13 +119,19 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
viewSizeInfo: sharer.getActiveViewSizeInfo()
|
||||
});
|
||||
sharer.setActiveStorage('scale', scale);
|
||||
if (!ignoreUpdateVisibleStatus) {
|
||||
this.#opts.calculator.updateVisiableStatus({
|
||||
viewScaleInfo: sharer.getActiveViewScaleInfo(),
|
||||
viewSizeInfo: sharer.getActiveViewSizeInfo()
|
||||
});
|
||||
}
|
||||
return { moveX, moveY };
|
||||
}
|
||||
|
||||
scroll(opts: { moveX: number; moveY: number }): ViewScaleInfo {
|
||||
scroll(opts: { moveX: number; moveY: number; ignoreUpdateVisibleStatus?: boolean }): ViewScaleInfo {
|
||||
const { sharer } = this.#opts;
|
||||
const prevViewScaleInfo: ViewScaleInfo = sharer.getActiveViewScaleInfo();
|
||||
const { moveX, moveY } = opts;
|
||||
const { moveX, moveY, ignoreUpdateVisibleStatus } = opts;
|
||||
const viewSizeInfo: ViewSizeInfo = sharer.getActiveViewSizeInfo();
|
||||
const viewScaleInfo = viewScroll({
|
||||
moveX,
|
||||
|
|
@ -120,6 +140,12 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
viewSizeInfo
|
||||
});
|
||||
sharer.setActiveViewScaleInfo(viewScaleInfo);
|
||||
if (!ignoreUpdateVisibleStatus) {
|
||||
this.#opts.calculator.updateVisiableStatus({
|
||||
viewScaleInfo: sharer.getActiveViewScaleInfo(),
|
||||
viewSizeInfo: sharer.getActiveViewSizeInfo()
|
||||
});
|
||||
}
|
||||
return viewScaleInfo;
|
||||
}
|
||||
|
||||
|
|
@ -130,10 +156,15 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
});
|
||||
|
||||
sharer.setActiveViewScaleInfo(viewScaleInfo);
|
||||
|
||||
this.#opts.calculator.updateVisiableStatus({
|
||||
viewScaleInfo: sharer.getActiveViewScaleInfo(),
|
||||
viewSizeInfo: sharer.getActiveViewSizeInfo()
|
||||
});
|
||||
return viewScaleInfo;
|
||||
}
|
||||
|
||||
resize(viewSize: Partial<ViewSizeInfo> = {}): ViewSizeInfo {
|
||||
resize(viewSize: Partial<ViewSizeInfo> = {}, opts?: { ignoreUpdateVisibleStatus?: boolean }): ViewSizeInfo {
|
||||
const { sharer } = this.#opts;
|
||||
const originViewSize = sharer.getActiveViewSizeInfo();
|
||||
const newViewSize = { ...originViewSize, ...viewSize };
|
||||
|
|
@ -155,6 +186,12 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
viewContext.canvas.height = height * devicePixelRatio;
|
||||
|
||||
sharer.setActiveViewSizeInfo(newViewSize);
|
||||
if (!opts?.ignoreUpdateVisibleStatus) {
|
||||
this.#opts.calculator.updateVisiableStatus({
|
||||
viewScaleInfo: sharer.getActiveViewScaleInfo(),
|
||||
viewSizeInfo: sharer.getActiveViewSizeInfo()
|
||||
});
|
||||
}
|
||||
return newViewSize;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Data, PointSize, CoreOptions, BoardMiddleware, ViewSizeInfo, CoreEventMap, ViewScaleInfo, LoadItemMap } from '@idraw/types';
|
||||
import type { Data, PointSize, CoreOptions, BoardMiddleware, ViewSizeInfo, CoreEventMap, ViewScaleInfo, LoadItemMap, ModifyOptions } from '@idraw/types';
|
||||
import { Board } from '@idraw/board';
|
||||
import { createBoardContent, validateElements } from '@idraw/util';
|
||||
import { Cursor } from './lib/cursor';
|
||||
|
|
@ -67,9 +67,14 @@ export class Core<E extends CoreEventMap = CoreEventMap> {
|
|||
this.#board.disuse(middleware);
|
||||
}
|
||||
|
||||
setData(data: Data) {
|
||||
setData(
|
||||
data: Data,
|
||||
opts?: {
|
||||
modifiedOptions?: ModifyOptions;
|
||||
}
|
||||
) {
|
||||
validateElements(data?.elements || []);
|
||||
this.#board.setData(data);
|
||||
this.#board.setData(data, opts);
|
||||
}
|
||||
|
||||
getData(): Data | null {
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
import type { Element, ElementSize, ViewRectInfo, ViewScaleInfo } from '@idraw/types';
|
||||
import { calcElementViewRectInfo } from '@idraw/util';
|
||||
|
||||
export function getElementViewRectInfo(
|
||||
elem: ElementSize,
|
||||
opts: {
|
||||
groupQueue: Element<'group'>[];
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
}
|
||||
): ViewRectInfo {
|
||||
const { groupQueue, viewScaleInfo } = opts;
|
||||
const viewRectInfo = calcElementViewRectInfo(elem, {
|
||||
groupQueue,
|
||||
viewScaleInfo,
|
||||
range: true
|
||||
});
|
||||
return viewRectInfo;
|
||||
}
|
||||
|
|
@ -10,6 +10,9 @@ export const keyHoverElementVertexes = Symbol(`${key}_hoverElementVertexes`); //
|
|||
export const keySelectedElementList = Symbol(`${key}_selectedElementList`); // Array<Element<ElementType>> | []
|
||||
export const keySelectedElementListVertexes = Symbol(`${key}_selectedElementListVertexes`); // ViewRectVertexes | null
|
||||
export const keySelectedElementController = Symbol(`${key}_selectedElementController`); // ElementSizeController
|
||||
export const keySelectedElementPosition = Symbol(`${key}_selectedElementPosition`); // ElementPosition | []
|
||||
export const keySelectedReferenceXLines = Symbol(`${key}_selectedReferenceXLines`); // Array<PointSize[]>
|
||||
export const keySelectedReferenceYLines = Symbol(`${key}_selectedReferenceYLines`); // Array<PointSize[]>
|
||||
export const keyGroupQueue = Symbol(`${key}_groupQueue`); // Array<Element<'group'>> | []
|
||||
export const keyGroupQueueVertexesList = Symbol(`${key}_groupQueueVertexesList`); // Array<ViewRectVertexes> | []
|
||||
export const keyIsMoving = Symbol(`${key}_isMoving`); // boolean | null
|
||||
|
|
@ -32,3 +35,5 @@ export const controllerSize = 10;
|
|||
// export const wrapperColor = '#1890ff';
|
||||
|
||||
export const auxiliaryColor = '#f7276e';
|
||||
|
||||
export const referenceColor = '#f7276e';
|
||||
|
|
|
|||
|
|
@ -1,37 +1,77 @@
|
|||
import type { ViewContext2D, ViewRectVertexes, Element, ViewScaleInfo } from '@idraw/types';
|
||||
import { getElementViewRectInfo } from './auxiliary';
|
||||
import type { ViewContext2D, Element, ViewScaleInfo, ViewSizeInfo, ViewCalculator, ViewRectInfo } from '@idraw/types';
|
||||
import { auxiliaryColor } from './config';
|
||||
import { drawLine, drawCrossByCenter } from './draw-base';
|
||||
|
||||
export function drawAuxiliaryLines(
|
||||
interface ViewBoxInfo {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
}
|
||||
|
||||
function getViewBoxInfo(rectInfo: ViewRectInfo): ViewBoxInfo {
|
||||
const boxInfo: ViewBoxInfo = {
|
||||
minX: rectInfo.topLeft.x,
|
||||
minY: rectInfo.topLeft.y,
|
||||
maxX: rectInfo.bottomRight.x,
|
||||
maxY: rectInfo.bottomRight.y,
|
||||
midX: rectInfo.center.x,
|
||||
midY: rectInfo.center.y
|
||||
};
|
||||
return boxInfo;
|
||||
}
|
||||
|
||||
export function drawAuxiliaryExperimentBox(
|
||||
ctx: ViewContext2D,
|
||||
opts: {
|
||||
vertexes: ViewRectVertexes;
|
||||
calculator: ViewCalculator;
|
||||
element: Element | null;
|
||||
groupQueue: Element<'group'>[];
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
) {
|
||||
const { element, groupQueue, viewScaleInfo } = opts;
|
||||
const { element, viewScaleInfo, viewSizeInfo, calculator } = opts;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const viewRectInfo = getElementViewRectInfo(element, {
|
||||
groupQueue,
|
||||
viewScaleInfo
|
||||
});
|
||||
const viewRectInfo = calculator.calcViewRectInfoFromRange(element.uuid, { viewScaleInfo, viewSizeInfo });
|
||||
if (!viewRectInfo) {
|
||||
return;
|
||||
}
|
||||
const lineOpts = {
|
||||
borderColor: auxiliaryColor,
|
||||
borderWidth: 1,
|
||||
lineDash: []
|
||||
};
|
||||
drawLine(ctx, viewRectInfo.topLeft, viewRectInfo.topRight, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.topRight, viewRectInfo.bottomRight, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.bottomRight, viewRectInfo.bottomLeft, lineOpts);
|
||||
drawLine(ctx, viewRectInfo.bottomLeft, viewRectInfo.topLeft, lineOpts);
|
||||
// drawLine(ctx, viewRectInfo.topLeft, viewRectInfo.topRight, lineOpts);
|
||||
// drawLine(ctx, viewRectInfo.topRight, viewRectInfo.bottomRight, lineOpts);
|
||||
// drawLine(ctx, viewRectInfo.bottomRight, viewRectInfo.bottomLeft, lineOpts);
|
||||
// drawLine(ctx, viewRectInfo.bottomLeft, viewRectInfo.topLeft, lineOpts);
|
||||
|
||||
// // vLine
|
||||
// drawLine(ctx, { x: viewRectInfo.topLeft.x, y: 0 }, { x: viewRectInfo.topLeft.x, y: viewSizeInfo.height }, lineOpts);
|
||||
// drawLine(ctx, { x: viewRectInfo.center.x, y: 0 }, { x: viewRectInfo.center.x, y: viewSizeInfo.height }, lineOpts);
|
||||
// drawLine(ctx, { x: viewRectInfo.bottomRight.x, y: 0 }, { x: viewRectInfo.bottomRight.x, y: viewSizeInfo.height }, lineOpts);
|
||||
// // hLine
|
||||
// drawLine(ctx, { x: 0, y: viewRectInfo.topLeft.y }, { x: viewSizeInfo.width, y: viewRectInfo.topLeft.y }, lineOpts);
|
||||
// drawLine(ctx, { x: 0, y: viewRectInfo.center.y }, { x: viewSizeInfo.width, y: viewRectInfo.center.y }, lineOpts);
|
||||
// drawLine(ctx, { x: 0, y: viewRectInfo.bottomRight.y }, { x: viewSizeInfo.width, y: viewRectInfo.bottomRight.y }, lineOpts);
|
||||
|
||||
const boxInfo = getViewBoxInfo(viewRectInfo);
|
||||
const { width, height } = viewSizeInfo;
|
||||
// vLine
|
||||
drawLine(ctx, { x: boxInfo.minX, y: 0 }, { x: boxInfo.minX, y: height }, lineOpts);
|
||||
drawLine(ctx, { x: boxInfo.midX, y: 0 }, { x: boxInfo.midX, y: height }, lineOpts);
|
||||
drawLine(ctx, { x: boxInfo.maxX, y: 0 }, { x: boxInfo.maxX, y: height }, lineOpts);
|
||||
// hLine
|
||||
drawLine(ctx, { x: 0, y: boxInfo.minY }, { x: width, y: boxInfo.minY }, lineOpts);
|
||||
drawLine(ctx, { x: 0, y: boxInfo.midY }, { x: width, y: boxInfo.midY }, lineOpts);
|
||||
drawLine(ctx, { x: 0, y: boxInfo.maxY }, { x: width, y: boxInfo.maxY }, lineOpts);
|
||||
|
||||
const crossOpts = { ...lineOpts, size: 6 };
|
||||
|
||||
drawCrossByCenter(ctx, viewRectInfo.center, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.topLeft, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.topRight, crossOpts);
|
||||
drawCrossByCenter(ctx, viewRectInfo.bottomLeft, crossOpts);
|
||||
|
|
|
|||
41
packages/core/src/middleware/selector/draw-reference.ts
Normal file
41
packages/core/src/middleware/selector/draw-reference.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import type { ViewContext2D, PointSize } from '@idraw/types';
|
||||
import { referenceColor } from './config';
|
||||
import { drawLine, drawCrossByCenter } from './draw-base';
|
||||
|
||||
export function drawReferenceLines(
|
||||
ctx: ViewContext2D,
|
||||
opts: {
|
||||
xLines?: Array<PointSize[]>;
|
||||
yLines?: Array<PointSize[]>;
|
||||
}
|
||||
) {
|
||||
const { xLines, yLines } = opts;
|
||||
const lineOpts = {
|
||||
borderColor: referenceColor,
|
||||
borderWidth: 1,
|
||||
lineDash: []
|
||||
};
|
||||
const crossOpts = { ...lineOpts, size: 6 };
|
||||
|
||||
if (xLines) {
|
||||
xLines.forEach((line) => {
|
||||
line.forEach((p, pIdx) => {
|
||||
drawCrossByCenter(ctx, p, crossOpts);
|
||||
if (line[pIdx + 1]) {
|
||||
drawLine(ctx, line[pIdx], line[pIdx + 1], lineOpts);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (yLines) {
|
||||
yLines.forEach((line) => {
|
||||
line.forEach((p, pIdx) => {
|
||||
drawCrossByCenter(ctx, p, crossOpts);
|
||||
if (line[pIdx + 1]) {
|
||||
drawLine(ctx, line[pIdx], line[pIdx + 1], lineOpts);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -7,13 +7,14 @@ import type {
|
|||
ViewRectVertexes,
|
||||
ViewScaleInfo,
|
||||
ViewSizeInfo,
|
||||
ElementSizeController
|
||||
ElementSizeController,
|
||||
ViewCalculator
|
||||
} from '@idraw/types';
|
||||
import { rotateElementVertexes, calcViewPointSize, calcViewVertexes } from '@idraw/util';
|
||||
import { rotateElementVertexes, calcViewPointSize, calcViewVertexes, calcViewElementSize } from '@idraw/util';
|
||||
import type { AreaSize } from './types';
|
||||
import { resizeControllerBorderWidth, areaBorderWidth, wrapperColor, selectWrapperBorderWidth, lockColor, controllerSize } from './config';
|
||||
import { drawVertexes, drawLine, drawCircleController, drawCrossVertexes } from './draw-base';
|
||||
// import { drawAuxiliaryLines } from './draw-auxiliary';
|
||||
// import { drawAuxiliaryExperimentBox } from './draw-auxiliary';
|
||||
|
||||
export function drawHoverVertexesWrapper(
|
||||
ctx: ViewContext2D,
|
||||
|
|
@ -70,14 +71,16 @@ export function drawSelectedElementControllersVertexes(
|
|||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
element: Element | null;
|
||||
groupQueue: Element<'group'>[];
|
||||
calculator: ViewCalculator;
|
||||
}
|
||||
) {
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
const { hideControllers } = opts;
|
||||
// const { element, groupQueue, viewScaleInfo } = opts;
|
||||
const {
|
||||
hideControllers
|
||||
// calculator, element, viewScaleInfo, viewSizeInfo
|
||||
} = opts;
|
||||
const { elementWrapper, topLeft, topRight, bottomLeft, bottomRight, top, rotate } = controller;
|
||||
const wrapperOpts = { borderColor: wrapperColor, borderWidth: selectWrapperBorderWidth, background: 'transparent', lineDash: [] };
|
||||
const ctrlOpts = { ...wrapperOpts, borderWidth: resizeControllerBorderWidth, background: '#FFFFFF' };
|
||||
|
|
@ -96,16 +99,11 @@ export function drawSelectedElementControllersVertexes(
|
|||
drawCircleController(ctx, calcViewPointSize(rotate.center, opts), { ...ctrlOpts, size: controllerSize, borderWidth: 2 });
|
||||
}
|
||||
|
||||
// drawAuxiliaryLines(ctx, {
|
||||
// vertexes: [
|
||||
// calcViewPointSize(topLeft.center, opts),
|
||||
// calcViewPointSize(topRight.center, opts),
|
||||
// calcViewPointSize(bottomRight.center, opts),
|
||||
// calcViewPointSize(bottomLeft.center, opts)
|
||||
// ],
|
||||
// drawAuxiliaryExperimentBox(ctx, {
|
||||
// calculator,
|
||||
// element,
|
||||
// groupQueue,
|
||||
// viewScaleInfo
|
||||
// viewScaleInfo,
|
||||
// viewSizeInfo
|
||||
// });
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +112,7 @@ export function drawElementListShadows(ctx: ViewContext2D, elements: Element<Ele
|
|||
let { x, y, w, h } = elem;
|
||||
const { angle = 0 } = elem;
|
||||
if (opts?.calculator) {
|
||||
const { calculator } = opts;
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts.viewScaleInfo, opts.viewSizeInfo);
|
||||
const size = calcViewElementSize({ x, y, w, h }, opts);
|
||||
x = size.x;
|
||||
y = size.y;
|
||||
w = size.w;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
is,
|
||||
calcElementsViewInfo,
|
||||
calcElementVertexesInGroup,
|
||||
calcElementQueueVertexesQueueInGroup,
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
drawGroupQueueVertexesWrappers,
|
||||
drawSelectedElementControllersVertexes
|
||||
} from './draw-wrapper';
|
||||
import { drawReferenceLines } from './draw-reference';
|
||||
import {
|
||||
getPointTarget,
|
||||
resizeElement,
|
||||
|
|
@ -54,6 +56,9 @@ import {
|
|||
keySelectedElementList,
|
||||
keySelectedElementListVertexes,
|
||||
keySelectedElementController,
|
||||
keySelectedElementPosition,
|
||||
keySelectedReferenceXLines,
|
||||
keySelectedReferenceYLines,
|
||||
keyIsMoving,
|
||||
controllerSize
|
||||
// keyDebugElemCenter,
|
||||
|
|
@ -63,6 +68,7 @@ import {
|
|||
// keyDebugStartHorizontal,
|
||||
// keyDebugStartVertical
|
||||
} from './config';
|
||||
import { calcReferenceInfo } from './reference';
|
||||
import { middlewareEventTextEdit } from '../text-editor';
|
||||
|
||||
export const middlewareEventSelect: string = '@middleware/select';
|
||||
|
|
@ -121,8 +127,10 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
viewScaleInfo: sharer.getActiveViewScaleInfo()
|
||||
});
|
||||
sharer.setSharedStorage(keySelectedElementController, controller);
|
||||
sharer.setSharedStorage(keySelectedElementPosition, getElementPositionFromList(list[0].uuid, sharer.getActiveStorage('data')?.elements || []));
|
||||
} else {
|
||||
sharer.setSharedStorage(keySelectedElementController, null);
|
||||
sharer.setSharedStorage(keySelectedElementPosition, []);
|
||||
}
|
||||
|
||||
if (opts?.triggerEvent === true) {
|
||||
|
|
@ -140,7 +148,8 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
viewSizeInfo: sharer.getActiveViewSizeInfo(),
|
||||
groupQueue: sharer.getSharedStorage(keyGroupQueue),
|
||||
areaSize: null,
|
||||
selectedElementController: sharer.getSharedStorage(keySelectedElementController)
|
||||
selectedElementController: sharer.getSharedStorage(keySelectedElementController),
|
||||
selectedElementPosition: sharer.getSharedStorage(keySelectedElementPosition)
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -156,6 +165,9 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
sharer.setSharedStorage(keySelectedElementList, []);
|
||||
sharer.setSharedStorage(keySelectedElementListVertexes, null);
|
||||
sharer.setSharedStorage(keySelectedElementController, null);
|
||||
sharer.setSharedStorage(keySelectedElementPosition, []);
|
||||
sharer.setSharedStorage(keySelectedReferenceXLines, []);
|
||||
sharer.setSharedStorage(keySelectedReferenceYLines, []);
|
||||
sharer.setSharedStorage(keyIsMoving, null);
|
||||
};
|
||||
|
||||
|
|
@ -392,6 +404,8 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
},
|
||||
|
||||
pointMove: (e: PointWatcherEvent) => {
|
||||
sharer.setSharedStorage(keySelectedReferenceXLines, []);
|
||||
sharer.setSharedStorage(keySelectedReferenceYLines, []);
|
||||
sharer.setSharedStorage(keyIsMoving, true);
|
||||
const data = sharer.getActiveStorage('data');
|
||||
const elems = getActiveElements();
|
||||
|
|
@ -408,9 +422,46 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
inBusyMode = 'drag';
|
||||
if (data && elems?.length === 1 && start && end && elems[0]?.operations?.lock !== true) {
|
||||
const { moveX, moveY } = calcMoveInGroup(start, end, groupQueue);
|
||||
elems[0].x += moveX / scale;
|
||||
elems[0].y += moveY / scale;
|
||||
|
||||
let totalMoveX = calculator.toGridNum(moveX / scale);
|
||||
let totalMoveY = calculator.toGridNum(moveY / scale);
|
||||
|
||||
const referenceInfo = calcReferenceInfo(elems[0].uuid, {
|
||||
calculator,
|
||||
data,
|
||||
groupQueue,
|
||||
viewScaleInfo,
|
||||
viewSizeInfo
|
||||
});
|
||||
try {
|
||||
if (referenceInfo) {
|
||||
if (is.x(referenceInfo.offsetX) && referenceInfo.offsetX !== null) {
|
||||
totalMoveX = calculator.toGridNum(totalMoveX + referenceInfo.offsetX);
|
||||
}
|
||||
if (is.y(referenceInfo.offsetY) && referenceInfo.offsetY !== null) {
|
||||
totalMoveY = calculator.toGridNum(totalMoveY + referenceInfo.offsetY);
|
||||
}
|
||||
sharer.setSharedStorage(keySelectedReferenceXLines, referenceInfo.xLines);
|
||||
sharer.setSharedStorage(keySelectedReferenceYLines, referenceInfo.yLines);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
elems[0].x = calculator.toGridNum(elems[0].x + totalMoveX);
|
||||
elems[0].y = calculator.toGridNum(elems[0].y + totalMoveY);
|
||||
updateSelectedElementList([elems[0]]);
|
||||
calculator.modifyViewVisibleInfoMap(data, {
|
||||
modifyOptions: {
|
||||
type: 'updateElement',
|
||||
content: {
|
||||
element: elems[0],
|
||||
position: sharer.getSharedStorage(keySelectedElementPosition) || []
|
||||
}
|
||||
},
|
||||
viewSizeInfo,
|
||||
viewScaleInfo
|
||||
});
|
||||
}
|
||||
viewer.drawFrame();
|
||||
} else if (actionType === 'drag-list') {
|
||||
|
|
@ -420,10 +471,23 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
const moveY = (end.y - start.y) / scale;
|
||||
elems.forEach((elem: Element<ElementType>) => {
|
||||
if (elem && elem?.operations?.lock !== true) {
|
||||
elem.x += moveX;
|
||||
elem.y += moveY;
|
||||
elem.x = calculator.toGridNum(elem.x + moveX);
|
||||
elem.y = calculator.toGridNum(elem.y + moveY);
|
||||
|
||||
calculator.modifyViewVisibleInfoMap(data, {
|
||||
modifyOptions: {
|
||||
type: 'updateElement',
|
||||
content: {
|
||||
element: elem,
|
||||
position: sharer.getSharedStorage(keySelectedElementPosition) || []
|
||||
}
|
||||
},
|
||||
viewSizeInfo,
|
||||
viewScaleInfo
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
sharer.setActiveStorage('data', data);
|
||||
}
|
||||
viewer.drawFrame();
|
||||
|
|
@ -472,23 +536,34 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
elems[0].angle = resizedElemSize.angle;
|
||||
} else {
|
||||
const resizedElemSize = resizeElement(elems[0], { scale, start: resizeStart, end: resizeEnd, resizeType, sharer });
|
||||
elems[0].x = resizedElemSize.x;
|
||||
elems[0].y = resizedElemSize.y;
|
||||
elems[0].x = calculator.toGridNum(resizedElemSize.x);
|
||||
elems[0].y = calculator.toGridNum(resizedElemSize.y);
|
||||
if (elems[0].type === 'group' && elems[0].operations?.deepResize === true) {
|
||||
// TODO
|
||||
// elems[0].w = resizedElemSize.w;
|
||||
// elems[0].h = resizedElemSize.h;
|
||||
deepResizeGroupElement(elems[0] as Element<'group'>, {
|
||||
w: resizedElemSize.w,
|
||||
h: resizedElemSize.h
|
||||
w: calculator.toGridNum(resizedElemSize.w),
|
||||
h: calculator.toGridNum(resizedElemSize.h)
|
||||
});
|
||||
} else {
|
||||
elems[0].w = resizedElemSize.w;
|
||||
elems[0].h = resizedElemSize.h;
|
||||
elems[0].w = calculator.toGridNum(resizedElemSize.w);
|
||||
elems[0].h = calculator.toGridNum(resizedElemSize.h);
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedElementList([elems[0]]);
|
||||
calculator.modifyViewVisibleInfoMap(data, {
|
||||
modifyOptions: {
|
||||
type: 'updateElement',
|
||||
content: {
|
||||
element: elems[0],
|
||||
position: sharer.getSharedStorage(keySelectedElementPosition) || []
|
||||
}
|
||||
},
|
||||
viewSizeInfo,
|
||||
viewScaleInfo
|
||||
});
|
||||
viewer.drawFrame();
|
||||
}
|
||||
} else if (actionType === 'area') {
|
||||
|
|
@ -505,7 +580,8 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
|
||||
pointEnd(e: PointWatcherEvent) {
|
||||
inBusyMode = null;
|
||||
|
||||
sharer.setSharedStorage(keySelectedReferenceXLines, []);
|
||||
sharer.setSharedStorage(keySelectedReferenceYLines, []);
|
||||
sharer.setSharedStorage(keyIsMoving, false);
|
||||
const data = sharer.getActiveStorage('data');
|
||||
const resizeType = sharer.getSharedStorage(keyResizeType);
|
||||
|
|
@ -669,9 +745,17 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
drawSelectedElementControllersVertexes(helperContext, selectedElementController, {
|
||||
...drawBaseOpts,
|
||||
element: elem,
|
||||
groupQueue,
|
||||
calculator,
|
||||
hideControllers: !!isMoving && actionType === 'drag'
|
||||
});
|
||||
if (actionType === 'drag') {
|
||||
const xLines = sharer.getSharedStorage(keySelectedReferenceXLines);
|
||||
const yLines = sharer.getSharedStorage(keySelectedReferenceYLines);
|
||||
drawReferenceLines(helperContext, {
|
||||
xLines,
|
||||
yLines
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in root
|
||||
|
|
@ -693,9 +777,17 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
|
|||
drawSelectedElementControllersVertexes(helperContext, selectedElementController, {
|
||||
...drawBaseOpts,
|
||||
element: elem,
|
||||
groupQueue,
|
||||
calculator,
|
||||
hideControllers: !!isMoving && actionType === 'drag'
|
||||
});
|
||||
if (actionType === 'drag') {
|
||||
const xLines = sharer.getSharedStorage(keySelectedReferenceXLines);
|
||||
const yLines = sharer.getSharedStorage(keySelectedReferenceYLines);
|
||||
drawReferenceLines(helperContext, {
|
||||
xLines,
|
||||
yLines
|
||||
});
|
||||
}
|
||||
} else if (actionType === 'area' && areaStart && areaEnd) {
|
||||
drawArea(helperContext, { start: areaStart, end: areaEnd });
|
||||
} else if ((['drag-list', 'drag-list-end'] as ActionType[]).includes(actionType)) {
|
||||
|
|
|
|||
337
packages/core/src/middleware/selector/reference.ts
Normal file
337
packages/core/src/middleware/selector/reference.ts
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
import type { Data, Element, PointSize, ViewRectInfo, ViewScaleInfo, ViewSizeInfo, ViewCalculator } from '@idraw/types';
|
||||
import { is } from '@idraw/util';
|
||||
|
||||
type DotMap = Record<number, number[]>;
|
||||
|
||||
type YLine = {
|
||||
x: number;
|
||||
yList: number[];
|
||||
};
|
||||
|
||||
type XLine = {
|
||||
xList: number[];
|
||||
y: number;
|
||||
};
|
||||
|
||||
interface ViewBoxInfo {
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
midX: number;
|
||||
midY: number;
|
||||
}
|
||||
|
||||
const unitSize = 2; // px
|
||||
|
||||
function getViewBoxInfo(rectInfo: ViewRectInfo): ViewBoxInfo {
|
||||
const boxInfo: ViewBoxInfo = {
|
||||
minX: rectInfo.topLeft.x,
|
||||
minY: rectInfo.topLeft.y,
|
||||
maxX: rectInfo.bottomRight.x,
|
||||
maxY: rectInfo.bottomRight.y,
|
||||
midX: rectInfo.center.x,
|
||||
midY: rectInfo.center.y
|
||||
};
|
||||
return boxInfo;
|
||||
}
|
||||
|
||||
const getClosestNumInSortedKeys = (sortedKeys: number[], target: number) => {
|
||||
if (sortedKeys.length === 0) {
|
||||
throw null;
|
||||
}
|
||||
if (sortedKeys.length === 1) {
|
||||
return sortedKeys[0];
|
||||
}
|
||||
|
||||
let left = 0;
|
||||
let right = sortedKeys.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (sortedKeys[mid] === target) {
|
||||
return sortedKeys[mid];
|
||||
} else if (sortedKeys[mid] < target) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (left >= sortedKeys.length) {
|
||||
return sortedKeys[right];
|
||||
}
|
||||
if (right < 0) {
|
||||
return sortedKeys[left];
|
||||
}
|
||||
|
||||
return Math.abs(sortedKeys[right] - target) <= Math.abs(sortedKeys[left] - target) ? sortedKeys[right] : sortedKeys[left];
|
||||
};
|
||||
|
||||
const isEqualNum = (a: number, b: number) => Math.abs(a - b) < 0.00001;
|
||||
|
||||
export function calcReferenceInfo(
|
||||
uuid: string,
|
||||
opts: {
|
||||
data: Data;
|
||||
groupQueue: Element<'group'>[];
|
||||
calculator: ViewCalculator;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
) {
|
||||
const { data, groupQueue, calculator, viewScaleInfo, viewSizeInfo } = opts;
|
||||
let targetElements: Element[] = data.elements || [];
|
||||
if (groupQueue?.length > 0) {
|
||||
targetElements = (groupQueue[groupQueue.length - 1] as Element<'group'>)?.detail?.children || [];
|
||||
}
|
||||
const siblingViewRectInfoList: ViewRectInfo[] = [];
|
||||
targetElements.forEach((elem: Element) => {
|
||||
if (elem.uuid !== uuid) {
|
||||
const info = calculator.calcViewRectInfoFromRange(elem.uuid, { checkVisible: true, viewScaleInfo, viewSizeInfo });
|
||||
if (info) {
|
||||
siblingViewRectInfoList.push(info);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const targetRectInfo = calculator.calcViewRectInfoFromRange(uuid, { viewScaleInfo, viewSizeInfo });
|
||||
|
||||
if (!targetRectInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const vTargetLineDotMap: DotMap = {}; // target vertical line dots
|
||||
const hTargetLineDotMap: DotMap = {}; // target horizontal line dots
|
||||
|
||||
const vRefLineDotMap: DotMap = {}; // reference vertical line dots
|
||||
const hRefLineDotMap: DotMap = {}; // reference horizontal line dots
|
||||
|
||||
const vHelperLineDotMapList: YLine[] = []; // vertical line list
|
||||
const hHelperLineDotMapList: XLine[] = []; // horizontal line list
|
||||
|
||||
let sortedRefXKeys: number[] = []; // hRefLineDotMap key nums
|
||||
let sortedRefYKeys: number[] = []; // vRefLineDotMap key nums
|
||||
|
||||
const targetBox = getViewBoxInfo(targetRectInfo);
|
||||
|
||||
vTargetLineDotMap[targetBox.minX] = [targetBox.minY, targetBox.midY, targetBox.maxY];
|
||||
vTargetLineDotMap[targetBox.midX] = [targetBox.minY, targetBox.midY, targetBox.maxY];
|
||||
vTargetLineDotMap[targetBox.maxX] = [targetBox.minY, targetBox.midY, targetBox.maxY];
|
||||
|
||||
hTargetLineDotMap[targetBox.minY] = [targetBox.minX, targetBox.midX, targetBox.maxX];
|
||||
hTargetLineDotMap[targetBox.midY] = [targetBox.minX, targetBox.midX, targetBox.maxX];
|
||||
hTargetLineDotMap[targetBox.maxY] = [targetBox.minX, targetBox.midX, targetBox.maxX];
|
||||
|
||||
siblingViewRectInfoList.forEach((info) => {
|
||||
const box = getViewBoxInfo(info);
|
||||
if (!vRefLineDotMap[box.minX]) {
|
||||
vRefLineDotMap[box.minX] = [];
|
||||
}
|
||||
if (!vRefLineDotMap[box.midX]) {
|
||||
vRefLineDotMap[box.midX] = [];
|
||||
}
|
||||
if (!vRefLineDotMap[box.maxX]) {
|
||||
vRefLineDotMap[box.maxX] = [];
|
||||
}
|
||||
if (!hRefLineDotMap[box.minY]) {
|
||||
hRefLineDotMap[box.minY] = [];
|
||||
}
|
||||
if (!hRefLineDotMap[box.midY]) {
|
||||
hRefLineDotMap[box.midY] = [];
|
||||
}
|
||||
if (!hRefLineDotMap[box.maxY]) {
|
||||
hRefLineDotMap[box.maxY] = [];
|
||||
}
|
||||
|
||||
vRefLineDotMap[box.minX] = [box.minY, box.midY, box.maxY];
|
||||
vRefLineDotMap[box.midX] = [box.minY, box.midY, box.maxY];
|
||||
vRefLineDotMap[box.maxX] = [box.minY, box.midY, box.maxY];
|
||||
|
||||
sortedRefXKeys.push(box.minX);
|
||||
sortedRefXKeys.push(box.midX);
|
||||
sortedRefXKeys.push(box.maxX);
|
||||
|
||||
hRefLineDotMap[box.minY] = [box.minX, box.midX, box.maxX];
|
||||
hRefLineDotMap[box.midY] = [box.minX, box.midX, box.maxX];
|
||||
hRefLineDotMap[box.maxY] = [box.minX, box.midX, box.maxX];
|
||||
|
||||
sortedRefYKeys.push(box.minY);
|
||||
sortedRefYKeys.push(box.midY);
|
||||
sortedRefYKeys.push(box.maxY);
|
||||
});
|
||||
|
||||
sortedRefXKeys = sortedRefXKeys.sort((a, b) => a - b);
|
||||
sortedRefYKeys = sortedRefYKeys.sort((a, b) => a - b);
|
||||
|
||||
let offsetX: number | null = null;
|
||||
let offsetY: number | null = null;
|
||||
let closestMinX: number | null = null;
|
||||
let closestMidX: number | null = null;
|
||||
let closestMaxX: number | null = null;
|
||||
let closestMinY: number | null = null;
|
||||
let closestMidY: number | null = null;
|
||||
let closestMaxY: number | null = null;
|
||||
|
||||
if (sortedRefXKeys.length > 0) {
|
||||
closestMinX = getClosestNumInSortedKeys(sortedRefXKeys, targetBox.minX);
|
||||
closestMidX = getClosestNumInSortedKeys(sortedRefXKeys, targetBox.midX);
|
||||
closestMaxX = getClosestNumInSortedKeys(sortedRefXKeys, targetBox.maxX);
|
||||
|
||||
const distMinX = Math.abs(closestMinX - targetBox.minX);
|
||||
const distMidX = Math.abs(closestMidX - targetBox.midX);
|
||||
const distMaxX = Math.abs(closestMaxX - targetBox.maxX);
|
||||
const closestXDist = Math.min(distMinX, distMidX, distMaxX);
|
||||
|
||||
if (closestXDist <= unitSize / viewScaleInfo.scale) {
|
||||
if (isEqualNum(closestXDist, distMinX)) {
|
||||
offsetX = closestMinX - targetBox.minX;
|
||||
} else if (isEqualNum(closestXDist, distMidX)) {
|
||||
offsetX = closestMidX - targetBox.midX;
|
||||
} else if (isEqualNum(closestXDist, distMaxX)) {
|
||||
offsetX = closestMaxX - targetBox.maxX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sortedRefYKeys.length > 0) {
|
||||
closestMinY = getClosestNumInSortedKeys(sortedRefYKeys, targetBox.minY);
|
||||
closestMidY = getClosestNumInSortedKeys(sortedRefYKeys, targetBox.midY);
|
||||
closestMaxY = getClosestNumInSortedKeys(sortedRefYKeys, targetBox.maxY);
|
||||
|
||||
const distMinY = Math.abs(closestMinY - targetBox.minY);
|
||||
const distMidY = Math.abs(closestMidY - targetBox.midY);
|
||||
const distMaxY = Math.abs(closestMaxY - targetBox.maxY);
|
||||
const closestYDist = Math.min(distMinY, distMidY, distMaxY);
|
||||
|
||||
if (closestYDist <= unitSize / viewScaleInfo.scale) {
|
||||
if (isEqualNum(closestYDist, distMinY)) {
|
||||
offsetY = closestMinY - targetBox.minY;
|
||||
} else if (isEqualNum(closestYDist, distMidY)) {
|
||||
offsetY = closestMidY - targetBox.midY;
|
||||
} else if (isEqualNum(closestYDist, distMaxY)) {
|
||||
offsetY = closestMaxY - targetBox.maxY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newTargetBox = { ...targetBox };
|
||||
if (offsetX !== null) {
|
||||
newTargetBox.minX += offsetX;
|
||||
newTargetBox.midX += offsetX;
|
||||
newTargetBox.maxX += offsetX;
|
||||
}
|
||||
if (offsetY !== null) {
|
||||
newTargetBox.minY += offsetY;
|
||||
newTargetBox.midY += offsetY;
|
||||
newTargetBox.maxY += offsetY;
|
||||
}
|
||||
|
||||
if (is.x(offsetX) && offsetX !== null && closestMinX !== null && closestMidX !== null && closestMaxX !== null) {
|
||||
if (isEqualNum(offsetX, closestMinX - targetBox.minX)) {
|
||||
const vLine: YLine = {
|
||||
x: closestMinX,
|
||||
yList: []
|
||||
};
|
||||
vLine.yList.push(newTargetBox.minY);
|
||||
vLine.yList.push(newTargetBox.midY);
|
||||
vLine.yList.push(newTargetBox.maxY);
|
||||
vLine.yList.push(...(hRefLineDotMap?.[closestMinX] || []));
|
||||
vHelperLineDotMapList.push(vLine);
|
||||
}
|
||||
|
||||
if (isEqualNum(offsetX, closestMidX - targetBox.minX)) {
|
||||
const vLine: YLine = {
|
||||
x: closestMidX,
|
||||
yList: []
|
||||
};
|
||||
vLine.yList.push(newTargetBox.minY);
|
||||
vLine.yList.push(newTargetBox.midY);
|
||||
vLine.yList.push(newTargetBox.maxY);
|
||||
vLine.yList.push(...(hRefLineDotMap?.[closestMidX] || []));
|
||||
vHelperLineDotMapList.push(vLine);
|
||||
}
|
||||
|
||||
if (isEqualNum(offsetX, closestMaxX - targetBox.minX)) {
|
||||
const vLine: YLine = {
|
||||
x: closestMaxX,
|
||||
yList: []
|
||||
};
|
||||
vLine.yList.push(newTargetBox.minY);
|
||||
vLine.yList.push(newTargetBox.midY);
|
||||
vLine.yList.push(newTargetBox.maxY);
|
||||
vLine.yList.push(...(hRefLineDotMap?.[closestMaxX] || []));
|
||||
vHelperLineDotMapList.push(vLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (is.y(offsetY) && offsetY !== null && closestMinY !== null && closestMidY !== null && closestMaxY !== null) {
|
||||
if (isEqualNum(offsetY, closestMinY - targetBox.minY)) {
|
||||
const hLine: XLine = {
|
||||
y: closestMinY,
|
||||
xList: []
|
||||
};
|
||||
hLine.xList.push(newTargetBox.minX);
|
||||
hLine.xList.push(newTargetBox.midX);
|
||||
hLine.xList.push(newTargetBox.maxX);
|
||||
hLine.xList.push(...(vRefLineDotMap?.[closestMinY] || []));
|
||||
hHelperLineDotMapList.push(hLine);
|
||||
}
|
||||
if (isEqualNum(offsetY, closestMidY - targetBox.midY)) {
|
||||
const hLine: XLine = {
|
||||
y: closestMidY,
|
||||
xList: []
|
||||
};
|
||||
hLine.xList.push(newTargetBox.minX);
|
||||
hLine.xList.push(newTargetBox.midX);
|
||||
hLine.xList.push(newTargetBox.maxX);
|
||||
hLine.xList.push(...(vRefLineDotMap?.[closestMinY] || []));
|
||||
hHelperLineDotMapList.push(hLine);
|
||||
}
|
||||
if (isEqualNum(offsetY, closestMaxY - targetBox.maxY)) {
|
||||
const hLine: XLine = {
|
||||
y: closestMaxY,
|
||||
xList: []
|
||||
};
|
||||
hLine.xList.push(newTargetBox.minX);
|
||||
hLine.xList.push(newTargetBox.midX);
|
||||
hLine.xList.push(newTargetBox.maxX);
|
||||
hLine.xList.push(...(vRefLineDotMap?.[closestMaxY] || []));
|
||||
hHelperLineDotMapList.push(hLine);
|
||||
}
|
||||
}
|
||||
|
||||
const yLines: Array<PointSize[]> = [];
|
||||
if (vHelperLineDotMapList?.length > 0) {
|
||||
vHelperLineDotMapList.forEach((item, i) => {
|
||||
yLines.push([]);
|
||||
item.yList.forEach((y) => {
|
||||
yLines[i].push({
|
||||
x: item.x,
|
||||
y
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const xLines: Array<PointSize[]> = [];
|
||||
if (hHelperLineDotMapList?.length > 0) {
|
||||
hHelperLineDotMapList.forEach((item, i) => {
|
||||
xLines.push([]);
|
||||
item.xList.forEach((x) => {
|
||||
xLines[i].push({
|
||||
x,
|
||||
y: item.y
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
offsetX,
|
||||
offsetY,
|
||||
yLines,
|
||||
xLines
|
||||
};
|
||||
}
|
||||
|
|
@ -1,25 +1,3 @@
|
|||
import type { ElementSizeController } from '@idraw/types';
|
||||
import {
|
||||
keyActionType,
|
||||
keyResizeType,
|
||||
keyAreaStart,
|
||||
keyAreaEnd,
|
||||
keyGroupQueue,
|
||||
keyGroupQueueVertexesList,
|
||||
keyHoverElement,
|
||||
keyHoverElementVertexes,
|
||||
keySelectedElementList,
|
||||
keySelectedElementListVertexes,
|
||||
keySelectedElementController,
|
||||
keyIsMoving,
|
||||
keyDebugElemCenter,
|
||||
keyDebugEnd0,
|
||||
keyDebugEndHorizontal,
|
||||
keyDebugEndVertical,
|
||||
keyDebugStartHorizontal,
|
||||
keyDebugStartVertical
|
||||
} from './config';
|
||||
|
||||
import {
|
||||
Data,
|
||||
ElementSize,
|
||||
|
|
@ -33,8 +11,33 @@ import {
|
|||
ViewCalculator,
|
||||
PointWatcherEvent,
|
||||
BoardMiddleware,
|
||||
ViewRectVertexes
|
||||
ViewRectVertexes,
|
||||
ElementSizeController,
|
||||
ElementPosition
|
||||
} from '@idraw/types';
|
||||
import {
|
||||
keyActionType,
|
||||
keyResizeType,
|
||||
keyAreaStart,
|
||||
keyAreaEnd,
|
||||
keyGroupQueue,
|
||||
keyGroupQueueVertexesList,
|
||||
keyHoverElement,
|
||||
keyHoverElementVertexes,
|
||||
keySelectedElementList,
|
||||
keySelectedElementListVertexes,
|
||||
keySelectedElementController,
|
||||
keySelectedElementPosition,
|
||||
keySelectedReferenceXLines,
|
||||
keySelectedReferenceYLines,
|
||||
keyIsMoving,
|
||||
keyDebugElemCenter,
|
||||
keyDebugEnd0,
|
||||
keyDebugEndHorizontal,
|
||||
keyDebugEndVertical,
|
||||
keyDebugStartHorizontal,
|
||||
keyDebugStartVertical
|
||||
} from './config';
|
||||
|
||||
export {
|
||||
Data,
|
||||
|
|
@ -96,6 +99,9 @@ export type DeepSelectorSharedStorage = {
|
|||
[keySelectedElementList]: Array<Element<ElementType>>;
|
||||
[keySelectedElementListVertexes]: ViewRectVertexes | null;
|
||||
[keySelectedElementController]: ElementSizeController | null;
|
||||
[keySelectedElementPosition]: ElementPosition;
|
||||
[keySelectedReferenceXLines]: Array<PointSize[]>;
|
||||
[keySelectedReferenceYLines]: Array<PointSize[]>;
|
||||
[keyIsMoving]: boolean | null;
|
||||
|
||||
[keyDebugElemCenter]: PointSize | null;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
calcElementVertexesInGroup,
|
||||
calcElementQueueVertexesQueueInGroup,
|
||||
calcViewPointSize,
|
||||
calcViewElementSize,
|
||||
rotatePointInGroup,
|
||||
rotatePoint,
|
||||
parseAngleToRadian,
|
||||
|
|
@ -44,11 +45,11 @@ export function isPointInViewActiveVertexes(
|
|||
p: PointSize,
|
||||
opts: { ctx: ViewContext2D; vertexes: ViewRectVertexes; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }
|
||||
): boolean {
|
||||
const { ctx, viewScaleInfo, viewSizeInfo, vertexes } = opts;
|
||||
const v0 = calcViewPointSize(vertexes[0], { viewScaleInfo, viewSizeInfo });
|
||||
const v1 = calcViewPointSize(vertexes[1], { viewScaleInfo, viewSizeInfo });
|
||||
const v2 = calcViewPointSize(vertexes[2], { viewScaleInfo, viewSizeInfo });
|
||||
const v3 = calcViewPointSize(vertexes[3], { viewScaleInfo, viewSizeInfo });
|
||||
const { ctx, viewScaleInfo, vertexes } = opts;
|
||||
const v0 = calcViewPointSize(vertexes[0], { viewScaleInfo });
|
||||
const v1 = calcViewPointSize(vertexes[1], { viewScaleInfo });
|
||||
const v2 = calcViewPointSize(vertexes[2], { viewScaleInfo });
|
||||
const v3 = calcViewPointSize(vertexes[3], { viewScaleInfo });
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(v0.x, v0.y);
|
||||
ctx.lineTo(v1.x, v1.y);
|
||||
|
|
@ -832,10 +833,9 @@ export function rotateElement(
|
|||
}
|
||||
): ElementSize {
|
||||
const { x, y, w, h, angle = 0 } = elem;
|
||||
const { center, start, end, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { center, start, end, viewScaleInfo } = opts;
|
||||
const elemCenter = calcViewPointSize(center, {
|
||||
viewScaleInfo,
|
||||
viewSizeInfo
|
||||
viewScaleInfo
|
||||
});
|
||||
const startAngle = limitAngle(angle);
|
||||
const changedRadian = calcRadian(elemCenter, start, end);
|
||||
|
|
@ -863,7 +863,7 @@ export function getSelectedListArea(
|
|||
const indexes: number[] = [];
|
||||
const uuids: string[] = [];
|
||||
const elements: Element<ElementType>[] = [];
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, start, end } = opts;
|
||||
const { viewScaleInfo, viewSizeInfo, start, end } = opts;
|
||||
|
||||
if (!(Array.isArray(data.elements) && start && end)) {
|
||||
return { indexes, uuids, elements };
|
||||
|
|
@ -878,7 +878,7 @@ export function getSelectedListArea(
|
|||
if (elem?.operations?.lock === true) {
|
||||
continue;
|
||||
}
|
||||
const elemSize = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo);
|
||||
const elemSize = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo });
|
||||
|
||||
const center = calcElementCenter(elemSize);
|
||||
if (center.x >= startX && center.x <= endX && center.y >= startY && center.y <= endY) {
|
||||
|
|
@ -914,7 +914,7 @@ export function calcSelectedElementsArea(
|
|||
return null;
|
||||
}
|
||||
const area: AreaSize = { x: 0, y: 0, w: 0, h: 0 };
|
||||
const { calculator, viewScaleInfo, viewSizeInfo } = opts;
|
||||
const { viewScaleInfo, viewSizeInfo } = opts;
|
||||
let prevElemSize: ElementSize | null = null;
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
|
|
@ -922,7 +922,7 @@ export function calcSelectedElementsArea(
|
|||
if (elem?.operations?.invisible) {
|
||||
continue;
|
||||
}
|
||||
const elemSize = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo);
|
||||
const elemSize = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo });
|
||||
|
||||
if (elemSize.angle && (elemSize.angle > 0 || elemSize.angle < 0)) {
|
||||
const ves = rotateElementVertexes(elemSize);
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { defaultSettings, getDefaultStorage, defaultMode } from './config';
|
|||
import { exportImageFileBlobURL } from './file';
|
||||
import type { ExportImageFileBaseOptions, ExportImageFileResult } from './file';
|
||||
import { eventKeys } from './event';
|
||||
import { changeMode } from './mode';
|
||||
import { changeMode, runMiddlewares } from './mode';
|
||||
|
||||
export class iDraw {
|
||||
#core: Core<IDrawEvent>;
|
||||
|
|
@ -54,15 +54,19 @@ export class iDraw {
|
|||
const core = this.#core;
|
||||
const store = this.#store;
|
||||
changeMode('select', core, store);
|
||||
this.enable('ruler');
|
||||
}
|
||||
|
||||
#setFeature(feat: IDrawFeature, status: boolean) {
|
||||
if (feat === 'ruler') {
|
||||
const store = this.#store;
|
||||
store.set('enableRuler', !!status);
|
||||
const currentMode = store.get('mode');
|
||||
this.setMode(currentMode);
|
||||
const store = this.#store;
|
||||
if (['ruler', 'scroll', 'scale'].includes(feat)) {
|
||||
const map: Record<IDrawFeature, keyof Omit<IDrawStorage, 'mode'>> = {
|
||||
ruler: 'enableRuler',
|
||||
scroll: 'enableScroll',
|
||||
scale: 'enableScale'
|
||||
};
|
||||
store.set(map[feat], !!status);
|
||||
runMiddlewares(this.#core, store);
|
||||
this.#core.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function isValidMode(mode: string | IDrawMode) {
|
|||
return ['select', 'drag', 'readOnly'].includes(mode);
|
||||
}
|
||||
|
||||
function runMiddlewares(core: Core<IDrawEvent>, store: Store<IDrawStorage>) {
|
||||
export function runMiddlewares(core: Core<IDrawEvent>, store: Store<IDrawStorage>) {
|
||||
const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = store.getSnapshot();
|
||||
if (enableScroll === true) {
|
||||
core.use(MiddlewareScroller);
|
||||
|
|
@ -52,7 +52,7 @@ export function changeMode(mode: IDrawMode, core: Core<IDrawEvent>, store: Store
|
|||
let enableSelect: boolean = false;
|
||||
let enableTextEdit: boolean = false;
|
||||
let enableDrag: boolean = false;
|
||||
let enableRuler = store.get('enableRuler');
|
||||
let enableRuler: boolean = false;
|
||||
|
||||
let innerMode: IDrawMode = 'select';
|
||||
store.set('mode', innerMode);
|
||||
|
|
@ -69,12 +69,14 @@ export function changeMode(mode: IDrawMode, core: Core<IDrawEvent>, store: Store
|
|||
enableSelect = true;
|
||||
enableTextEdit = true;
|
||||
enableDrag = false;
|
||||
enableRuler = true;
|
||||
} else if (innerMode === 'drag') {
|
||||
enableScale = true;
|
||||
enableScroll = true;
|
||||
enableSelect = false;
|
||||
enableTextEdit = false;
|
||||
enableDrag = true;
|
||||
enableRuler = true;
|
||||
} else if (innerMode === 'readOnly') {
|
||||
enableScale = false;
|
||||
enableScroll = false;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { rotateElement, calcViewElementSize } from '@idraw/util';
|
||||
import { createColorStyle } from './color';
|
||||
import { drawBoxShadow, getOpacity } from './box';
|
||||
|
||||
export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: RendererDrawElementOptions) {
|
||||
const { detail, angle } = elem;
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { background = '#000000', borderColor = '#000000', boxSizing, borderWidth = 0 } = detail;
|
||||
let bw: number = 0;
|
||||
if (typeof borderWidth === 'number' && borderWidth > 0) {
|
||||
|
|
@ -16,7 +16,7 @@ export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: Re
|
|||
bw = bw * viewScaleInfo.scale;
|
||||
|
||||
// const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = viewScaleInfo;
|
||||
const { x, y, w, h } = calculator?.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { x, y, w, h } = calcViewElementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Element, ElementType, ElementSize, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement, calcViewBoxSize } from '@idraw/util';
|
||||
import { rotateElement, calcViewBoxSize, calcViewElementSize } from '@idraw/util';
|
||||
import { drawCircle } from './circle';
|
||||
import { drawRect } from './rect';
|
||||
import { drawImage } from './image';
|
||||
|
|
@ -70,8 +70,8 @@ export function drawElement(ctx: ViewContext2D, elem: Element<ElementType>, opts
|
|||
}
|
||||
|
||||
export function drawGroup(ctx: ViewContext2D, elem: Element<'group'>, opts: RendererDrawElementOptions) {
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h, angle: elem.angle }, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h, angle: elem.angle }, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
ctx.globalAlpha = getOpacity(elem) * parentOpacity;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { rotateElement, calcViewElementSize } from '@idraw/util';
|
||||
import { getOpacity } from './box';
|
||||
|
||||
export function drawHTML(ctx: ViewContext2D, elem: Element<'html'>, opts: RendererDrawElementOptions) {
|
||||
const content = opts.loader.getContent(elem);
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
if (!content && !opts.loader.isDestroyed()) {
|
||||
opts.loader.load(elem as Element<'html'>, opts.elementAssets || {});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement, calcViewBoxSize } from '@idraw/util';
|
||||
import { rotateElement, calcViewBoxSize, calcViewElementSize } from '@idraw/util';
|
||||
import { drawBox, drawBoxShadow, getOpacity } from './box';
|
||||
|
||||
export function drawImage(ctx: ViewContext2D, elem: Element<'image'>, opts: RendererDrawElementOptions) {
|
||||
const content = opts.loader.getContent(elem);
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement, generateSVGPath } from '@idraw/util';
|
||||
import { rotateElement, generateSVGPath, calcViewElementSize } from '@idraw/util';
|
||||
import { drawBox, drawBoxShadow } from './box';
|
||||
|
||||
export function drawPath(ctx: ViewContext2D, elem: Element<'path'>, opts: RendererDrawElementOptions) {
|
||||
const { detail } = elem;
|
||||
const { originX, originY, originW, originH } = detail;
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
const scaleW = w / originW;
|
||||
const scaleH = h / originH;
|
||||
const viewOriginX = originX * scaleW;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { rotateElement, calcViewElementSize } from '@idraw/util';
|
||||
import { drawBox, drawBoxShadow } from './box';
|
||||
|
||||
export function drawRect(ctx: ViewContext2D, elem: Element<'rect'>, opts: RendererDrawElementOptions) {
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { rotateElement, calcViewElementSize } from '@idraw/util';
|
||||
import { getOpacity } from './box';
|
||||
|
||||
export function drawSVG(ctx: ViewContext2D, elem: Element<'svg'>, opts: RendererDrawElementOptions) {
|
||||
const content = opts.loader.getContent(elem);
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
if (!content && !opts.loader.isDestroyed()) {
|
||||
opts.loader.load(elem as Element<'svg'>, opts.elementAssets || {});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
|
||||
import { rotateElement } from '@idraw/util';
|
||||
import { rotateElement, calcViewElementSize } from '@idraw/util';
|
||||
import { is, isColorStr, getDefaultElementDetailConfig } from '@idraw/util';
|
||||
import { drawBox } from './box';
|
||||
|
||||
const detailConfig = getDefaultElementDetailConfig();
|
||||
|
||||
export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: RendererDrawElementOptions) {
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
drawBox(ctx, viewElem, {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import type { RendererDrawElementOptions, ViewContext2D, DataUnderlay } from '@idraw/types';
|
||||
import { calcViewElementSize } from '@idraw/util';
|
||||
import { drawBox, drawBoxShadow } from './box';
|
||||
|
||||
export function drawUnderlay(ctx: ViewContext2D, underlay: DataUnderlay, opts: RendererDrawElementOptions) {
|
||||
const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
|
||||
const elem = { uuid: 'underlay', ...underlay };
|
||||
const { x, y, w, h } = calculator?.elementSize(elem, viewScaleInfo, viewSizeInfo) || elem;
|
||||
const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem;
|
||||
const angle = 0;
|
||||
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
|
||||
drawBoxShadow(ctx, viewElem, {
|
||||
|
|
|
|||
|
|
@ -125,14 +125,32 @@ export interface BoardViewerOptions {
|
|||
afterDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot<Record<any | symbol, any>> }) => void;
|
||||
}
|
||||
|
||||
// export interface BoardViewerStorage {
|
||||
// viewVisibleInfoMap: ViewVisibleInfoMap;
|
||||
// }
|
||||
|
||||
export interface BoardViewer extends UtilEventEmitter<BoardViewerEventMap> {
|
||||
drawFrame(): void;
|
||||
scale(opts: { scale: number; point: PointSize }): { moveX: number; moveY: number };
|
||||
scroll(opts: { moveX?: number; moveY?: number }): ViewScaleInfo;
|
||||
// scrollX(num: number): ViewScaleInfo;
|
||||
// scrollY(num: number): ViewScaleInfo;
|
||||
resize(viewSize: Partial<ViewSizeInfo>): ViewSizeInfo;
|
||||
scale(opts: { scale: number; point: PointSize; ignoreUpdateVisibleStatus?: boolean }): { moveX: number; moveY: number };
|
||||
scroll(opts: { moveX?: number; moveY?: number; ignoreUpdateVisibleStatus?: boolean }): ViewScaleInfo;
|
||||
resize(viewSize: Partial<ViewSizeInfo>, opts?: { ignoreUpdateVisibleStatus?: boolean }): ViewSizeInfo;
|
||||
updateViewScaleInfo(opts: { scale: number; offsetX: number; offsetY: number }): ViewScaleInfo;
|
||||
|
||||
// resetViewVisibleInfoMap(
|
||||
// data: Data,
|
||||
// opts: {
|
||||
// viewScaleInfo: ViewScaleInfo;
|
||||
// viewSizeInfo: ViewSizeInfo;
|
||||
// }
|
||||
// ): void;
|
||||
// modifyViewVisibleInfoMap(
|
||||
// data: Data,
|
||||
// opts: {
|
||||
// modifyOptions: ModifyOptions;
|
||||
// viewScaleInfo: ViewScaleInfo;
|
||||
// viewSizeInfo: ViewSizeInfo;
|
||||
// }
|
||||
// ): void;
|
||||
}
|
||||
|
||||
export interface BoardRenderer extends UtilEventEmitter<RendererEventMap> {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { CoreOptions } from './core';
|
|||
|
||||
export type IDrawMode = 'select' | 'drag' | 'readOnly';
|
||||
|
||||
export type IDrawFeature = 'ruler' | string; // TODO other feature
|
||||
export type IDrawFeature = 'ruler' | 'scroll' | 'scale'; // TODO other feature
|
||||
|
||||
export interface IDrawSettings {
|
||||
mode?: IDrawMode;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export interface ModifyContentMap {
|
|||
moveElement: { from: ElementPosition; to: ElementPosition };
|
||||
}
|
||||
|
||||
export interface ModifyOptions<T extends ModifyType> {
|
||||
export interface ModifyOptions<T extends ModifyType = ModifyType> {
|
||||
type: T;
|
||||
content: ModifyContentMap[T];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ export type ActiveStore = ViewSizeInfo &
|
|||
export interface StoreSharer<S extends Record<any, any> = any> {
|
||||
getActiveStorage<T extends keyof ActiveStore>(key: T): ActiveStore[T];
|
||||
setActiveStorage<T extends keyof ActiveStore>(key: T, storage: ActiveStore[T]): void;
|
||||
getActiveStoreSnapshot(): ActiveStore;
|
||||
getActiveStoreSnapshot(opts?: { deepClone?: boolean }): ActiveStore;
|
||||
getSharedStorage<K extends keyof S = string>(key: K): S[K];
|
||||
setSharedStorage<K extends keyof S = string>(key: K, storage: S[K]): void;
|
||||
getSharedStoreSnapshot(): Record<string, any>;
|
||||
getSharedStoreSnapshot(opts?: { deepClone?: boolean }): Record<string, any>;
|
||||
|
||||
getActiveViewScaleInfo(): ViewScaleInfo;
|
||||
setActiveViewScaleInfo(viewScaleInfo: ViewScaleInfo): void;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import type { Element, ElementType, ElementSize, ElementPosition } from './element';
|
||||
import type { Element, ElementType, ElementPosition } from './element';
|
||||
import type { Point, PointSize } from './point';
|
||||
import type { Data } from './data';
|
||||
import type { ViewContext2D } from './context2d';
|
||||
import type { ModifyOptions } from './modify';
|
||||
|
||||
export interface ViewScaleInfo {
|
||||
scale: number;
|
||||
|
|
@ -35,14 +36,53 @@ export interface ViewCalculatorOptions {
|
|||
viewContext: ViewContext2D;
|
||||
}
|
||||
|
||||
export interface ViewCalculatorStorage {
|
||||
viewVisibleInfoMap: ViewVisibleInfoMap;
|
||||
visibleCount: number;
|
||||
invisibleCount: number;
|
||||
}
|
||||
|
||||
export interface ViewCalculator {
|
||||
isElementInView(elem: Element<ElementType>, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean;
|
||||
isPointInElement(p: Point, elem: Element<ElementType>, viewScaleInfo: ViewScaleInfo, viewSize: ViewSizeInfo): boolean;
|
||||
elementSize(size: ElementSize, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ElementSize;
|
||||
getPointElement(
|
||||
p: Point,
|
||||
opts: { data: Data; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; groupQueue?: Element<'group'>[] }
|
||||
): { index: number; element: null | Element<ElementType>; groupQueueIndex: number };
|
||||
resetViewVisibleInfoMap(
|
||||
data: Data,
|
||||
opts: {
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): void;
|
||||
updateVisiableStatus(opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): void;
|
||||
calcViewRectInfoFromOrigin(
|
||||
uuid: string,
|
||||
opts: {
|
||||
checkVisible?: boolean;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): ViewRectInfo | null;
|
||||
calcViewRectInfoFromRange(
|
||||
uuid: string,
|
||||
opts: {
|
||||
checkVisible?: boolean;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): ViewRectInfo | null;
|
||||
modifyViewVisibleInfoMap(
|
||||
data: Data,
|
||||
opts: {
|
||||
modifyOptions: ModifyOptions;
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): void;
|
||||
|
||||
toGridNum(num: number): number;
|
||||
}
|
||||
|
||||
export type ViewRectVertexes = [PointSize, PointSize, PointSize, PointSize];
|
||||
|
|
@ -69,8 +109,7 @@ export type ViewRectInfo = {
|
|||
|
||||
export type ViewRectInfoMap = {
|
||||
originRectInfo: ViewRectInfo;
|
||||
viewRectInfo: ViewRectInfo | null;
|
||||
rangeRectInfo: ViewRectInfo | null;
|
||||
rangeRectInfo: ViewRectInfo;
|
||||
};
|
||||
|
||||
export type ViewVisibleInfo = ViewRectInfoMap & {
|
||||
|
|
|
|||
|
|
@ -58,9 +58,10 @@ export {
|
|||
calcViewScaleInfo,
|
||||
calcElementViewRectInfo,
|
||||
calcElementOriginRectInfo,
|
||||
calcElementViewRectInfoMap
|
||||
calcElementViewRectInfoMap,
|
||||
originRectInfoToRangeRectInfo
|
||||
} from './lib/view-calc';
|
||||
export { sortElementsViewVisiableInfoMap } from './lib/view-visible';
|
||||
export { sortElementsViewVisiableInfoMap, calcVisibleOriginCanvasRectInfo, updateViewVisibleInfoMapStatus } from './lib/view-visible';
|
||||
export { rotatePoint, rotateVertexes, rotateByCenter } from './lib/rotate';
|
||||
export { getElementVertexes, calcElementVertexesInGroup, calcElementVertexesQueueInGroup, calcElementQueueVertexesQueueInGroup } from './lib/vertex';
|
||||
export { calcElementSizeController } from './lib/controller';
|
||||
|
|
|
|||
|
|
@ -298,7 +298,8 @@ export function getGroupQueueByElementPosition(elements: Element<ElementType>[],
|
|||
let currentElements: Element[] = elements;
|
||||
if (position.length > 1) {
|
||||
for (let i = 0; i < position.length - 1; i++) {
|
||||
const group = currentElements[i] as Element<'group'>;
|
||||
const index = position[i];
|
||||
const group = currentElements[index] as Element<'group'>;
|
||||
if (group?.type === 'group' && Array.isArray(group?.detail?.children)) {
|
||||
groupQueue.push(group);
|
||||
currentElements = group.detail.children;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import type { ElementSize } from '@idraw/types';
|
||||
|
||||
export function checkRectIntersect(rect1: ElementSize, rect2: ElementSize) {
|
||||
const react1MinX = rect1.x;
|
||||
const react1MinY = rect1.y;
|
||||
const react1MaxX = rect1.x + rect1.w;
|
||||
const react1MaxY = rect1.y + rect1.h;
|
||||
const rect1MinX = rect1.x;
|
||||
const rect1MinY = rect1.y;
|
||||
const rect1MaxX = rect1.x + rect1.w;
|
||||
const rect1MaxY = rect1.y + rect1.h;
|
||||
|
||||
const react2MinX = rect2.x;
|
||||
const react2MinY = rect2.y;
|
||||
const react2MaxX = rect2.x + rect2.w;
|
||||
const react2MaxY = rect2.y + rect2.h;
|
||||
const rect2MinX = rect2.x;
|
||||
const rect2MinY = rect2.y;
|
||||
const rect2MaxX = rect2.x + rect2.w;
|
||||
const rect2MaxY = rect2.y + rect2.h;
|
||||
|
||||
return react1MinX <= react2MaxX && react1MaxX >= react2MinX && react1MinY <= react2MaxY && react1MaxY >= react2MinY;
|
||||
return rect1MinX <= rect2MaxX && rect1MaxX >= rect2MinX && rect1MinY <= rect2MaxY && rect1MaxY >= rect2MinY;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ export class Store<T extends Record<string | symbol, any> = Record<string | symb
|
|||
return this.#temp[name];
|
||||
}
|
||||
|
||||
getSnapshot(): T {
|
||||
// return deepClone(this.#temp);
|
||||
return this.#temp;
|
||||
getSnapshot(opts?: { deepClone?: boolean }): T {
|
||||
if (opts?.deepClone === true) {
|
||||
return deepClone(this.#temp);
|
||||
}
|
||||
return { ...this.#temp };
|
||||
}
|
||||
|
||||
clear() {
|
||||
|
|
|
|||
|
|
@ -284,6 +284,37 @@ export function calcElementOriginRectInfo(
|
|||
return rectInfo;
|
||||
}
|
||||
|
||||
export function originRectInfoToRangeRectInfo(originRectInfo: ViewRectInfo): ViewRectInfo {
|
||||
const rangeMaxX = Math.max(originRectInfo.topLeft.x, originRectInfo.topRight.x, originRectInfo.bottomRight.x, originRectInfo.bottomLeft.x);
|
||||
const rangeMaxY = Math.max(originRectInfo.topLeft.y, originRectInfo.topRight.y, originRectInfo.bottomRight.y, originRectInfo.bottomLeft.y);
|
||||
const rangeMinX = Math.min(originRectInfo.topLeft.x, originRectInfo.topRight.x, originRectInfo.bottomRight.x, originRectInfo.bottomLeft.x);
|
||||
const rangeMinY = Math.min(originRectInfo.topLeft.y, originRectInfo.topRight.y, originRectInfo.bottomRight.y, originRectInfo.bottomLeft.y);
|
||||
|
||||
const rangeCenter = { x: originRectInfo.center.x, y: originRectInfo.center.y };
|
||||
const rangeTopLeft = { x: rangeMinX, y: rangeMinY };
|
||||
const rangeTopRight = { x: rangeMaxX, y: rangeMinY };
|
||||
const rangeBottomRight = { x: rangeMaxX, y: rangeMaxY };
|
||||
const rangeBottomLeft = { x: rangeMinX, y: rangeMaxY };
|
||||
|
||||
const rangeTop = getCenterFromTwoPoints(rangeTopLeft, rangeTopRight);
|
||||
const rangeBottom = getCenterFromTwoPoints(rangeBottomLeft, rangeBottomRight);
|
||||
const rangeLeft = getCenterFromTwoPoints(rangeTopLeft, rangeBottomLeft);
|
||||
const rangeRight = getCenterFromTwoPoints(rangeTopRight, rangeBottomRight);
|
||||
|
||||
const rangeRectInfo: ViewRectInfo = {
|
||||
center: rangeCenter,
|
||||
topLeft: rangeTopLeft,
|
||||
topRight: rangeTopRight,
|
||||
bottomLeft: rangeBottomLeft,
|
||||
bottomRight: rangeBottomRight,
|
||||
top: rangeTop,
|
||||
right: rangeRight,
|
||||
left: rangeLeft,
|
||||
bottom: rangeBottom
|
||||
};
|
||||
return rangeRectInfo;
|
||||
}
|
||||
|
||||
export function calcElementViewRectInfo(
|
||||
elemSize: ElementSize,
|
||||
opts: {
|
||||
|
|
|
|||
|
|
@ -1,40 +1,50 @@
|
|||
import { Element, ElementPosition, Elements, ViewRectInfo, ViewVisibleInfoMap, ViewVisibleInfo } from '@idraw/types';
|
||||
import { calcElementOriginRectInfo } from './view-calc';
|
||||
import { Element, ElementPosition, Elements, ViewScaleInfo, ViewSizeInfo, ViewRectInfo, ViewVisibleInfoMap, ViewVisibleInfo } from '@idraw/types';
|
||||
import { calcElementOriginRectInfo, originRectInfoToRangeRectInfo } from './view-calc';
|
||||
import { getGroupQueueByElementPosition } from './element';
|
||||
import { calcElementCenter } from './rotate';
|
||||
import { is } from './is';
|
||||
|
||||
export function sortElementsViewVisiableInfoMap(elements: Elements): ViewVisibleInfoMap {
|
||||
export function sortElementsViewVisiableInfoMap(
|
||||
elements: Elements,
|
||||
opts: {
|
||||
viewScaleInfo: ViewScaleInfo;
|
||||
viewSizeInfo: ViewSizeInfo;
|
||||
}
|
||||
): {
|
||||
viewVisibleInfoMap: ViewVisibleInfoMap;
|
||||
visibleCount: number;
|
||||
invisibleCount: number;
|
||||
} {
|
||||
const visibleInfoMap: ViewVisibleInfoMap = {};
|
||||
const currentPosition: ElementPosition = [];
|
||||
|
||||
const _walk = (elem: Element) => {
|
||||
const baseInfo: Omit<ViewVisibleInfo, 'originRectInfo'> = {
|
||||
viewRectInfo: null,
|
||||
rangeRectInfo: null,
|
||||
const baseInfo: Omit<ViewVisibleInfo, 'originRectInfo' | 'rangeRectInfo'> = {
|
||||
isVisibleInView: true,
|
||||
isGroup: elem.type === 'group',
|
||||
position: [...currentPosition]
|
||||
};
|
||||
let originRectInfo: ViewRectInfo | null = null;
|
||||
|
||||
const groupQueue = getGroupQueueByElementPosition(elements, currentPosition);
|
||||
|
||||
originRectInfo = calcElementOriginRectInfo(elem, {
|
||||
groupQueue: groupQueue || []
|
||||
});
|
||||
|
||||
visibleInfoMap[elem.uuid] = {
|
||||
...baseInfo,
|
||||
...{
|
||||
originRectInfo: originRectInfo as ViewRectInfo
|
||||
originRectInfo: originRectInfo as ViewRectInfo,
|
||||
rangeRectInfo: is.angle(elem.angle) ? originRectInfoToRangeRectInfo(originRectInfo as ViewRectInfo) : originRectInfo
|
||||
}
|
||||
};
|
||||
|
||||
if (elem.type === 'group') {
|
||||
(elem as Element<'group'>).detail.children.forEach((ele, i) => {
|
||||
if (ele.type === 'group') {
|
||||
currentPosition.push(i);
|
||||
}
|
||||
currentPosition.push(i);
|
||||
_walk(ele);
|
||||
if (ele.type === 'group') {
|
||||
currentPosition.pop();
|
||||
}
|
||||
currentPosition.pop();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -45,5 +55,99 @@ export function sortElementsViewVisiableInfoMap(elements: Elements): ViewVisible
|
|||
currentPosition.pop();
|
||||
});
|
||||
|
||||
return visibleInfoMap;
|
||||
return updateViewVisibleInfoMapStatus(visibleInfoMap, opts);
|
||||
}
|
||||
|
||||
function isRangeRectInfoCollide(info1: ViewRectInfo, info2: ViewRectInfo): boolean {
|
||||
const rect1MinX = Math.min(info1.topLeft.x, info1.topRight.x, info1.bottomLeft.x, info1.bottomRight.x);
|
||||
const rect1MaxX = Math.max(info1.topLeft.x, info1.topRight.x, info1.bottomLeft.x, info1.bottomRight.x);
|
||||
const rect1MinY = Math.min(info1.topLeft.y, info1.topRight.y, info1.bottomLeft.y, info1.bottomRight.y);
|
||||
const rect1MaxY = Math.max(info1.topLeft.y, info1.topRight.y, info1.bottomLeft.y, info1.bottomRight.y);
|
||||
|
||||
const rect2MinX = Math.min(info2.topLeft.x, info2.topRight.x, info2.bottomLeft.x, info2.bottomRight.x);
|
||||
const rect2MaxX = Math.max(info2.topLeft.x, info2.topRight.x, info2.bottomLeft.x, info2.bottomRight.x);
|
||||
const rect2MinY = Math.min(info2.topLeft.y, info2.topRight.y, info2.bottomLeft.y, info2.bottomRight.y);
|
||||
const rect2MaxY = Math.max(info2.topLeft.y, info2.topRight.y, info2.bottomLeft.y, info2.bottomRight.y);
|
||||
|
||||
if (
|
||||
(rect1MinX <= rect2MaxX && rect1MaxX >= rect2MinX && rect1MinY <= rect2MaxY && rect1MaxY >= rect2MinY) ||
|
||||
(rect2MaxX <= rect1MaxY && rect2MaxX >= rect1MaxY && rect2MaxX <= rect1MaxY && rect2MaxX >= rect1MaxY)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// function logViewVisibleInfoMapStatus(viewVisibleInfoMap: ViewVisibleInfoMap) {
|
||||
// console.log('------------------------------------------------');
|
||||
// Object.keys(viewVisibleInfoMap).forEach((uuid) => {
|
||||
// const item = viewVisibleInfoMap[uuid];
|
||||
// const info = item.originRectInfo;
|
||||
// const rect = {
|
||||
// x: info.topLeft.x,
|
||||
// y: info.topRight.y,
|
||||
// w: info.bottomRight.x - info.topLeft.x,
|
||||
// h: info.bottomRight.y - info.topLeft.y
|
||||
// };
|
||||
// console.log('view: ', uuid, item.isVisibleInView, rect);
|
||||
// });
|
||||
// }
|
||||
|
||||
export function updateViewVisibleInfoMapStatus(
|
||||
viewVisibleInfoMap: ViewVisibleInfoMap,
|
||||
opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }
|
||||
): {
|
||||
viewVisibleInfoMap: ViewVisibleInfoMap;
|
||||
visibleCount: number;
|
||||
invisibleCount: number;
|
||||
} {
|
||||
const canvasRectInfo = calcVisibleOriginCanvasRectInfo(opts);
|
||||
let visibleCount = 0;
|
||||
let invisibleCount = 0;
|
||||
Object.keys(viewVisibleInfoMap).forEach((uuid) => {
|
||||
const info = viewVisibleInfoMap[uuid];
|
||||
info.isVisibleInView = isRangeRectInfoCollide(info.rangeRectInfo, canvasRectInfo);
|
||||
info.isVisibleInView ? visibleCount++ : invisibleCount++;
|
||||
});
|
||||
|
||||
// logViewVisibleInfoMapStatus(viewVisibleInfoMap);
|
||||
|
||||
return { viewVisibleInfoMap, visibleCount, invisibleCount };
|
||||
}
|
||||
|
||||
export function calcVisibleOriginCanvasRectInfo(opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): ViewRectInfo {
|
||||
const { viewScaleInfo, viewSizeInfo } = opts;
|
||||
// console.log('xxx ===== ', viewScaleInfo, viewSizeInfo);
|
||||
const { scale, offsetTop, offsetLeft } = viewScaleInfo;
|
||||
const { width, height } = viewSizeInfo;
|
||||
|
||||
const x = 0 - offsetLeft / scale;
|
||||
const y = 0 - offsetTop / scale;
|
||||
const w = width / scale;
|
||||
const h = height / scale;
|
||||
|
||||
const center = calcElementCenter({ x, y, w, h });
|
||||
const topLeft = { x, y };
|
||||
const topRight = { x: x + w, y };
|
||||
const bottomLeft = { x, y: y + h };
|
||||
const bottomRight = { x: x + w, y: y + h };
|
||||
const left = { x, y: center.y };
|
||||
const top = { x: center.x, y };
|
||||
const right = { x: x + w, y: center.y };
|
||||
const bottom = { x: center.x, y: y + h };
|
||||
const rectInfo: ViewRectInfo = {
|
||||
center,
|
||||
topLeft,
|
||||
topRight,
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom
|
||||
};
|
||||
return rectInfo;
|
||||
}
|
||||
|
||||
// export function isInVisiableView(rangeRectInfo: ViewRectInfo) {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue