diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index 6ba515c..a490983 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -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 { 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 { } } - 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 }); diff --git a/packages/board/src/lib/calculator.ts b/packages/board/src/lib/calculator.ts index 4d39c82..59609fb 100644 --- a/packages/board/src/lib/calculator.ts +++ b/packages/board/src/lib/calculator.ts @@ -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; constructor(opts: ViewCalculatorOptions) { this.#opts = opts; + this.#store = new Store({ + 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); + } } diff --git a/packages/board/src/lib/sharer.ts b/packages/board/src/lib/sharer.ts index 038c67d..ae178f4 100644 --- a/packages/board/src/lib/sharer.ts +++ b/packages/board/src/lib/sharer.ts @@ -40,8 +40,8 @@ export class Sharer implements StoreSharer 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 return this.#sharedStore.set(key, storage); } - getSharedStoreSnapshot(): Record { - return this.#sharedStore.getSnapshot(); + getSharedStoreSnapshot(opts?: { deepClone?: boolean }): Record { + return this.#sharedStore.getSnapshot(opts); } // get/set active info diff --git a/packages/board/src/lib/viewer.ts b/packages/board/src/lib/viewer.ts index b1f4156..ba6e184 100644 --- a/packages/board/src/lib/viewer.ts +++ b/packages/board/src/lib/viewer.ts @@ -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 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 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 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 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 implements BoardVi }); sharer.setActiveViewScaleInfo(viewScaleInfo); + + this.#opts.calculator.updateVisiableStatus({ + viewScaleInfo: sharer.getActiveViewScaleInfo(), + viewSizeInfo: sharer.getActiveViewSizeInfo() + }); return viewScaleInfo; } - resize(viewSize: Partial = {}): ViewSizeInfo { + resize(viewSize: Partial = {}, 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 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; } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7b7645b..f89b440 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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 { 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 { diff --git a/packages/core/src/middleware/selector/auxiliary.ts b/packages/core/src/middleware/selector/auxiliary.ts deleted file mode 100644 index 1bba63d..0000000 --- a/packages/core/src/middleware/selector/auxiliary.ts +++ /dev/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; -} diff --git a/packages/core/src/middleware/selector/config.ts b/packages/core/src/middleware/selector/config.ts index f55de56..a55157b 100644 --- a/packages/core/src/middleware/selector/config.ts +++ b/packages/core/src/middleware/selector/config.ts @@ -10,6 +10,9 @@ export const keyHoverElementVertexes = Symbol(`${key}_hoverElementVertexes`); // export const keySelectedElementList = Symbol(`${key}_selectedElementList`); // Array> | [] 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 +export const keySelectedReferenceYLines = Symbol(`${key}_selectedReferenceYLines`); // Array export const keyGroupQueue = Symbol(`${key}_groupQueue`); // Array> | [] export const keyGroupQueueVertexesList = Symbol(`${key}_groupQueueVertexesList`); // Array | [] 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'; diff --git a/packages/core/src/middleware/selector/draw-auxiliary.ts b/packages/core/src/middleware/selector/draw-auxiliary.ts index c72998d..9bd73be 100644 --- a/packages/core/src/middleware/selector/draw-auxiliary.ts +++ b/packages/core/src/middleware/selector/draw-auxiliary.ts @@ -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); diff --git a/packages/core/src/middleware/selector/draw-reference.ts b/packages/core/src/middleware/selector/draw-reference.ts new file mode 100644 index 0000000..00ef495 --- /dev/null +++ b/packages/core/src/middleware/selector/draw-reference.ts @@ -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; + yLines?: Array; + } +) { + 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); + } + }); + }); + } +} diff --git a/packages/core/src/middleware/selector/draw-wrapper.ts b/packages/core/src/middleware/selector/draw-wrapper.ts index 185ab0f..61af35b 100644 --- a/packages/core/src/middleware/selector/draw-wrapper.ts +++ b/packages/core/src/middleware/selector/draw-wrapper.ts @@ -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 { + 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) => { 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, { - 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; + +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 = []; + if (vHelperLineDotMapList?.length > 0) { + vHelperLineDotMapList.forEach((item, i) => { + yLines.push([]); + item.yList.forEach((y) => { + yLines[i].push({ + x: item.x, + y + }); + }); + }); + } + + const xLines: Array = []; + 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 + }; +} diff --git a/packages/core/src/middleware/selector/types.ts b/packages/core/src/middleware/selector/types.ts index 858cb48..4194ce3 100644 --- a/packages/core/src/middleware/selector/types.ts +++ b/packages/core/src/middleware/selector/types.ts @@ -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>; [keySelectedElementListVertexes]: ViewRectVertexes | null; [keySelectedElementController]: ElementSizeController | null; + [keySelectedElementPosition]: ElementPosition; + [keySelectedReferenceXLines]: Array; + [keySelectedReferenceYLines]: Array; [keyIsMoving]: boolean | null; [keyDebugElemCenter]: PointSize | null; diff --git a/packages/core/src/middleware/selector/util.ts b/packages/core/src/middleware/selector/util.ts index 80b6a69..b1d083f 100644 --- a/packages/core/src/middleware/selector/util.ts +++ b/packages/core/src/middleware/selector/util.ts @@ -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[] = []; - 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); diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index ce91698..1311da4 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -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; @@ -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> = { + ruler: 'enableRuler', + scroll: 'enableScroll', + scale: 'enableScale' + }; + store.set(map[feat], !!status); + runMiddlewares(this.#core, store); + this.#core.refresh(); } } diff --git a/packages/idraw/src/mode.ts b/packages/idraw/src/mode.ts index a9b1473..545390c 100644 --- a/packages/idraw/src/mode.ts +++ b/packages/idraw/src/mode.ts @@ -7,7 +7,7 @@ function isValidMode(mode: string | IDrawMode) { return ['select', 'drag', 'readOnly'].includes(mode); } -function runMiddlewares(core: Core, store: Store) { +export function runMiddlewares(core: Core, store: Store) { 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, 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, 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; diff --git a/packages/renderer/src/draw/circle.ts b/packages/renderer/src/draw/circle.ts index 88c9db4..f7e6e0a 100644 --- a/packages/renderer/src/draw/circle.ts +++ b/packages/renderer/src/draw/circle.ts @@ -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 }, () => { diff --git a/packages/renderer/src/draw/group.ts b/packages/renderer/src/draw/group.ts index 6a63ddc..197b5db 100644 --- a/packages/renderer/src/draw/group.ts +++ b/packages/renderer/src/draw/group.ts @@ -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, 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; diff --git a/packages/renderer/src/draw/html.ts b/packages/renderer/src/draw/html.ts index 3b8a1a6..304f642 100644 --- a/packages/renderer/src/draw/html.ts +++ b/packages/renderer/src/draw/html.ts @@ -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 || {}); diff --git a/packages/renderer/src/draw/image.ts b/packages/renderer/src/draw/image.ts index 12184bb..c8ac92a 100644 --- a/packages/renderer/src/draw/image.ts +++ b/packages/renderer/src/draw/image.ts @@ -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 }, () => { diff --git a/packages/renderer/src/draw/path.ts b/packages/renderer/src/draw/path.ts index 893b75a..cfc5615 100644 --- a/packages/renderer/src/draw/path.ts +++ b/packages/renderer/src/draw/path.ts @@ -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; diff --git a/packages/renderer/src/draw/rect.ts b/packages/renderer/src/draw/rect.ts index b71c84f..a2977cc 100644 --- a/packages/renderer/src/draw/rect.ts +++ b/packages/renderer/src/draw/rect.ts @@ -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 }, () => { diff --git a/packages/renderer/src/draw/svg.ts b/packages/renderer/src/draw/svg.ts index dfcec8a..374ae01 100644 --- a/packages/renderer/src/draw/svg.ts +++ b/packages/renderer/src/draw/svg.ts @@ -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 || {}); diff --git a/packages/renderer/src/draw/text.ts b/packages/renderer/src/draw/text.ts index 77d5bdf..ebf9f41 100644 --- a/packages/renderer/src/draw/text.ts +++ b/packages/renderer/src/draw/text.ts @@ -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, { diff --git a/packages/renderer/src/draw/underlay.ts b/packages/renderer/src/draw/underlay.ts index 0a020b6..85f5289 100644 --- a/packages/renderer/src/draw/underlay.ts +++ b/packages/renderer/src/draw/underlay.ts @@ -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, { diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index 142c5e6..9c0152e 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -125,14 +125,32 @@ export interface BoardViewerOptions { afterDrawFrame: (e: { snapshot: BoardViewerFrameSnapshot> }) => void; } +// export interface BoardViewerStorage { +// viewVisibleInfoMap: ViewVisibleInfoMap; +// } + export interface BoardViewer extends UtilEventEmitter { 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; + scale(opts: { scale: number; point: PointSize; ignoreUpdateVisibleStatus?: boolean }): { moveX: number; moveY: number }; + scroll(opts: { moveX?: number; moveY?: number; ignoreUpdateVisibleStatus?: boolean }): ViewScaleInfo; + resize(viewSize: Partial, 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 { diff --git a/packages/types/src/lib/idraw.ts b/packages/types/src/lib/idraw.ts index 803da15..0538767 100644 --- a/packages/types/src/lib/idraw.ts +++ b/packages/types/src/lib/idraw.ts @@ -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; diff --git a/packages/types/src/lib/modify.ts b/packages/types/src/lib/modify.ts index e8705bb..2a0ff85 100644 --- a/packages/types/src/lib/modify.ts +++ b/packages/types/src/lib/modify.ts @@ -14,7 +14,7 @@ export interface ModifyContentMap { moveElement: { from: ElementPosition; to: ElementPosition }; } -export interface ModifyOptions { +export interface ModifyOptions { type: T; content: ModifyContentMap[T]; } diff --git a/packages/types/src/lib/store.ts b/packages/types/src/lib/store.ts index 647bc6c..46c90b1 100644 --- a/packages/types/src/lib/store.ts +++ b/packages/types/src/lib/store.ts @@ -14,10 +14,10 @@ export type ActiveStore = ViewSizeInfo & export interface StoreSharer = any> { getActiveStorage(key: T): ActiveStore[T]; setActiveStorage(key: T, storage: ActiveStore[T]): void; - getActiveStoreSnapshot(): ActiveStore; + getActiveStoreSnapshot(opts?: { deepClone?: boolean }): ActiveStore; getSharedStorage(key: K): S[K]; setSharedStorage(key: K, storage: S[K]): void; - getSharedStoreSnapshot(): Record; + getSharedStoreSnapshot(opts?: { deepClone?: boolean }): Record; getActiveViewScaleInfo(): ViewScaleInfo; setActiveViewScaleInfo(viewScaleInfo: ViewScaleInfo): void; diff --git a/packages/types/src/lib/view.ts b/packages/types/src/lib/view.ts index cc2348a..3a00698 100644 --- a/packages/types/src/lib/view.ts +++ b/packages/types/src/lib/view.ts @@ -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, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean; isPointInElement(p: Point, elem: Element, 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; 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 & { diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 903ea2f..889288a 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -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'; diff --git a/packages/util/src/lib/element.ts b/packages/util/src/lib/element.ts index 46d0d32..1fb80d8 100644 --- a/packages/util/src/lib/element.ts +++ b/packages/util/src/lib/element.ts @@ -298,7 +298,8 @@ export function getGroupQueueByElementPosition(elements: Element[], 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; diff --git a/packages/util/src/lib/rect.ts b/packages/util/src/lib/rect.ts index 2de51f3..f923d98 100644 --- a/packages/util/src/lib/rect.ts +++ b/packages/util/src/lib/rect.ts @@ -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; } diff --git a/packages/util/src/lib/store.ts b/packages/util/src/lib/store.ts index 1cd24b2..7a66a1c 100644 --- a/packages/util/src/lib/store.ts +++ b/packages/util/src/lib/store.ts @@ -17,9 +17,11 @@ export class Store = Record { - const baseInfo: Omit = { - viewRectInfo: null, - rangeRectInfo: null, + const baseInfo: Omit = { 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) {}