feat: add reference lines for middleware select

This commit is contained in:
chenshenhai 2024-03-16 21:53:11 +08:00
parent 3befb12deb
commit 66843c1568
36 changed files with 1152 additions and 205 deletions

View file

@ -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 });

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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;
}

View file

@ -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';

View file

@ -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);

View 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);
}
});
});
}
}

View file

@ -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;

View file

@ -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)) {

View 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
};
}

View file

@ -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;

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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 }, () => {

View file

@ -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;

View file

@ -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 || {});

View file

@ -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 }, () => {

View file

@ -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;

View file

@ -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 }, () => {

View file

@ -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 || {});

View file

@ -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, {

View file

@ -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, {

View file

@ -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> {

View file

@ -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;

View file

@ -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];
}

View file

@ -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;

View file

@ -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 & {

View file

@ -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';

View file

@ -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;

View file

@ -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;
}

View file

@ -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() {

View file

@ -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: {

View file

@ -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) {}