From 378f9c4cbfaad744409b8890b1965b30e3dda5ef Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 23 Mar 2024 09:53:06 +0800 Subject: [PATCH] feat: add info middleware --- packages/board/src/lib/calculator.ts | 19 ++- packages/board/src/lib/viewer.ts | 2 + packages/core/src/index.ts | 1 + .../core/src/middleware/info/draw-info.ts | 130 ++++++++++++++++ packages/core/src/middleware/info/index.ts | 140 ++++++++++++++++++ packages/core/src/middleware/info/types.ts | 4 + packages/core/src/middleware/ruler/index.ts | 9 +- packages/core/src/middleware/ruler/types.ts | 4 + packages/core/src/middleware/ruler/util.ts | 66 ++++++++- .../core/src/middleware/scroller/index.ts | 3 +- .../core/src/middleware/scroller/types.ts | 10 ++ packages/core/src/middleware/scroller/util.ts | 2 +- .../core/src/middleware/selector/index.ts | 28 ++-- packages/idraw/src/config.ts | 3 +- packages/idraw/src/idraw.ts | 5 +- packages/idraw/src/mode.ts | 21 ++- packages/renderer/src/draw/box.ts | 8 +- packages/renderer/src/draw/elements.ts | 2 +- packages/renderer/src/draw/group.ts | 2 +- packages/renderer/src/draw/index.ts | 2 +- packages/renderer/src/draw/layout.ts | 51 +++++++ packages/renderer/src/draw/underlay.ts | 27 ---- packages/renderer/src/index.ts | 33 +++-- packages/types/src/lib/data.ts | 9 +- packages/types/src/lib/idraw.ts | 3 +- packages/types/src/lib/view.ts | 7 +- packages/util/src/lib/view-calc.ts | 8 +- packages/util/src/lib/view-content.ts | 62 ++++---- 28 files changed, 550 insertions(+), 111 deletions(-) create mode 100644 packages/core/src/middleware/info/draw-info.ts create mode 100644 packages/core/src/middleware/info/index.ts create mode 100644 packages/core/src/middleware/info/types.ts create mode 100644 packages/core/src/middleware/ruler/types.ts create mode 100644 packages/core/src/middleware/scroller/types.ts create mode 100644 packages/renderer/src/draw/layout.ts delete mode 100644 packages/renderer/src/draw/underlay.ts diff --git a/packages/board/src/lib/calculator.ts b/packages/board/src/lib/calculator.ts index 59609fb..6bdca24 100644 --- a/packages/board/src/lib/calculator.ts +++ b/packages/board/src/lib/calculator.ts @@ -6,7 +6,6 @@ import type { ViewCalculator, ViewCalculatorOptions, ViewScaleInfo, - ElementSize, ViewSizeInfo, ViewCalculatorStorage, ViewRectInfo, @@ -17,7 +16,6 @@ import { is, isViewPointInElement, getViewPointAtElement, - isElementInView, Store, sortElementsViewVisiableInfoMap, updateViewVisibleInfoMapStatus, @@ -43,7 +41,10 @@ export class Calculator implements ViewCalculator { }); } - toGridNum(num: number): number { + toGridNum(num: number, opts?: { ignore?: boolean }): number { + if (opts?.ignore === true) { + return num; + } // TODO // const gridUnitSize = 1; // px; return Math.round(num); @@ -53,10 +54,18 @@ export class Calculator implements ViewCalculator { this.#opts = null as any; } - isElementInView(elem: ElementSize, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean { - return isElementInView(elem, { viewScaleInfo, viewSizeInfo }); + needRender(elem: Element): boolean { + const viewVisibleInfoMap = this.#store.get('viewVisibleInfoMap'); + const info = viewVisibleInfoMap[elem.uuid]; + if (!info) { + return true; + } + return info.isVisibleInView; } + /** + * @deprecated + */ isPointInElement(p: Point, elem: Element, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean { const context2d = this.#opts.viewContext; return isViewPointInElement(p, { diff --git a/packages/board/src/lib/viewer.ts b/packages/board/src/lib/viewer.ts index ba6e184..6395017 100644 --- a/packages/board/src/lib/viewer.ts +++ b/packages/board/src/lib/viewer.ts @@ -102,6 +102,8 @@ export class Viewer extends EventEmitter implements BoardVi const { sharer } = this.#opts; const activeStore: ActiveStore = sharer.getActiveStoreSnapshot(); const sharedStore: Record = sharer.getSharedStoreSnapshot(); + // const activeStore: ActiveStore = sharer.getActiveStoreSnapshot({ deepClone: true }); + // const sharedStore: Record = sharer.getSharedStoreSnapshot({ deepClone: true }); this.#drawFrameSnapshotQueue.push({ activeStore, sharedStore diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index f89b440..ea6668e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -10,6 +10,7 @@ export { MiddlewareScaler, middlewareEventScale } from './middleware/scaler'; export { MiddlewareRuler, middlewareEventRuler } from './middleware/ruler'; export { MiddlewareTextEditor, middlewareEventTextEdit, middlewareEventTextChange } from './middleware/text-editor'; export { MiddlewareDragger } from './middleware/dragger'; +export { MiddlewareInfo } from './middleware/info'; export class Core { #board: Board; diff --git a/packages/core/src/middleware/info/draw-info.ts b/packages/core/src/middleware/info/draw-info.ts new file mode 100644 index 0000000..6bca131 --- /dev/null +++ b/packages/core/src/middleware/info/draw-info.ts @@ -0,0 +1,130 @@ +import type { PointSize, ViewContext2D } from '@idraw/types'; +import { rotateByCenter } from '@idraw/util'; + +const fontFamily = 'monospace'; + +export function drawSizeInfoText( + ctx: ViewContext2D, + opts: { point: PointSize; rotateCenter: PointSize; angle: number; text: string; fontSize: number; lineHeight: number; color: string; background: string } +) { + const { point, rotateCenter, angle, text, color, background, fontSize, lineHeight } = opts; + + rotateByCenter(ctx, angle, rotateCenter, () => { + ctx.$setFont({ + fontWeight: '300', + fontSize, + fontFamily + }); + const padding = (lineHeight - fontSize) / 2; + const textWidth = ctx.$undoPixelRatio(ctx.measureText(text).width); + const bgStart = { + x: point.x - textWidth / 2 - padding, + y: point.y + }; + const bgEnd = { + x: bgStart.x + textWidth + padding * 2, + y: bgStart.y + fontSize + padding + }; + const textStart = { + x: point.x - textWidth / 2, + y: point.y + }; + ctx.setLineDash([]); + ctx.fillStyle = background; + ctx.beginPath(); + ctx.moveTo(bgStart.x, bgStart.y); + ctx.lineTo(bgEnd.x, bgStart.y); + ctx.lineTo(bgEnd.x, bgEnd.y); + ctx.lineTo(bgStart.x, bgEnd.y); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color; + ctx.textBaseline = 'top'; + ctx.fillText(text, textStart.x, textStart.y + padding); + }); +} + +export function drawPositionInfoText( + ctx: ViewContext2D, + opts: { point: PointSize; rotateCenter: PointSize; angle: number; text: string; fontSize: number; lineHeight: number; color: string; background: string } +) { + const { point, rotateCenter, angle, text, color, background, fontSize, lineHeight } = opts; + + rotateByCenter(ctx, angle, rotateCenter, () => { + ctx.$setFont({ + fontWeight: '300', + fontSize, + fontFamily + }); + const padding = (lineHeight - fontSize) / 2; + const textWidth = ctx.$undoPixelRatio(ctx.measureText(text).width); + const bgStart = { + x: point.x, + y: point.y + }; + const bgEnd = { + x: bgStart.x + textWidth + padding * 2, + y: bgStart.y + fontSize + padding + }; + const textStart = { + x: point.x + padding, + y: point.y + }; + ctx.setLineDash([]); + ctx.fillStyle = background; + ctx.beginPath(); + ctx.moveTo(bgStart.x, bgStart.y); + ctx.lineTo(bgEnd.x, bgStart.y); + ctx.lineTo(bgEnd.x, bgEnd.y); + ctx.lineTo(bgStart.x, bgEnd.y); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color; + ctx.textBaseline = 'top'; + ctx.fillText(text, textStart.x, textStart.y + padding); + }); +} + +export function drawAngleInfoText( + ctx: ViewContext2D, + opts: { point: PointSize; rotateCenter: PointSize; angle: number; text: string; fontSize: number; lineHeight: number; color: string; background: string } +) { + const { point, rotateCenter, angle, text, color, background, fontSize, lineHeight } = opts; + + rotateByCenter(ctx, angle, rotateCenter, () => { + ctx.$setFont({ + fontWeight: '300', + fontSize, + fontFamily + }); + const padding = (lineHeight - fontSize) / 2; + const textWidth = ctx.$undoPixelRatio(ctx.measureText(text).width); + const bgStart = { + x: point.x, + y: point.y + }; + const bgEnd = { + x: bgStart.x + textWidth + padding * 2, + y: bgStart.y + fontSize + padding + }; + const textStart = { + x: point.x + padding, + y: point.y + }; + ctx.setLineDash([]); + ctx.fillStyle = background; + ctx.beginPath(); + ctx.moveTo(bgStart.x, bgStart.y); + ctx.lineTo(bgEnd.x, bgStart.y); + ctx.lineTo(bgEnd.x, bgEnd.y); + ctx.lineTo(bgStart.x, bgEnd.y); + ctx.closePath(); + ctx.fill(); + + ctx.fillStyle = color; + ctx.textBaseline = 'top'; + ctx.fillText(text, textStart.x, textStart.y + padding); + }); +} diff --git a/packages/core/src/middleware/info/index.ts b/packages/core/src/middleware/info/index.ts new file mode 100644 index 0000000..fc566ea --- /dev/null +++ b/packages/core/src/middleware/info/index.ts @@ -0,0 +1,140 @@ +import type { BoardMiddleware, ViewRectInfo, Element } from '@idraw/types'; +import { formatNumber, getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot, createUUID, limitAngle, rotatePoint, parseAngleToRadian } from '@idraw/util'; +import { keySelectedElementList, keyActionType, keyGroupQueue } from '../selector'; +import { drawSizeInfoText, drawPositionInfoText, drawAngleInfoText } from './draw-info'; +import type { DeepInfoSharedStorage } from './types'; + +const infoBackground = '#1973bac6'; +const infoTextColor = '#ffffff'; +const infoFontSize = 10; +const infoLineHeight = 16; + +export const MiddlewareInfo: BoardMiddleware = (opts) => { + const { boardContent, calculator } = opts; + const { helperContext } = boardContent; + + return { + name: '@middleware/info', + + beforeDrawFrame({ snapshot }) { + const { sharedStore } = snapshot; + + const selectedElementList = sharedStore[keySelectedElementList]; + const actionType = sharedStore[keyActionType]; + const groupQueue = sharedStore[keyGroupQueue] || []; + + // console.log('resizeType ===== ', resizeType); + + if (selectedElementList.length === 1) { + const elem = selectedElementList[0]; + if (elem && ['select', 'drag', 'resize'].includes(actionType as string)) { + const viewScaleInfo = getViewScaleInfoFromSnapshot(snapshot); + const viewSizeInfo = getViewSizeInfoFromSnapshot(snapshot); + const { x, y, w, h, angle } = elem; + const totalGroupQueue = [ + ...groupQueue, + ...[ + { + uuid: createUUID(), + x, + y, + w, + h, + angle, + type: 'group', + detail: { children: [] } + } as Element<'group'> + ] + ]; + + const calcOpts = { viewScaleInfo, viewSizeInfo }; + + const rangeRectInfo = calculator.calcViewRectInfoFromOrigin(elem.uuid, calcOpts); + let totalAngle = 0; + totalGroupQueue.forEach((group) => { + totalAngle += group.angle || 0; + }); + const totalRadian = parseAngleToRadian(limitAngle(0 - totalAngle)); + + if (rangeRectInfo) { + const elemCenter = rangeRectInfo?.center; + const rectInfo: ViewRectInfo = { + topLeft: rotatePoint(elemCenter, rangeRectInfo.topLeft, totalRadian), + topRight: rotatePoint(elemCenter, rangeRectInfo.topRight, totalRadian), + bottomRight: rotatePoint(elemCenter, rangeRectInfo.bottomRight, totalRadian), + bottomLeft: rotatePoint(elemCenter, rangeRectInfo.bottomLeft, totalRadian), + center: rotatePoint(elemCenter, rangeRectInfo.center, totalRadian), + top: rotatePoint(elemCenter, rangeRectInfo.top, totalRadian), + right: rotatePoint(elemCenter, rangeRectInfo.right, totalRadian), + bottom: rotatePoint(elemCenter, rangeRectInfo.bottom, totalRadian), + left: rotatePoint(elemCenter, rangeRectInfo.left, totalRadian) + }; + + const x = formatNumber(elem.x, { decimalPlaces: 2 }); + const y = formatNumber(elem.y, { decimalPlaces: 2 }); + const w = formatNumber(elem.w, { decimalPlaces: 2 }); + const h = formatNumber(elem.h, { decimalPlaces: 2 }); + + // // test start ---- + // const ctx = helperContext; + // ctx.beginPath(); + // ctx.moveTo(rectInfo.topLeft.x, rectInfo.topLeft.y); + // ctx.lineTo(rectInfo.topRight.x, rectInfo.topRight.y); + // ctx.lineTo(rectInfo.bottomRight.x, rectInfo.bottomRight.y); + // ctx.lineTo(rectInfo.bottomLeft.x, rectInfo.bottomLeft.y); + // ctx.closePath(); + // ctx.strokeStyle = 'red'; + // ctx.stroke(); + // // test end ---- + + const xyText = `${formatNumber(x, { decimalPlaces: 0 })},${formatNumber(y, { decimalPlaces: 0 })}`; + const whText = `${formatNumber(w, { decimalPlaces: 0 })}x${formatNumber(h, { decimalPlaces: 0 })}`; + const angleText = `${formatNumber(elem.angle || 0, { decimalPlaces: 0 })}°`; + + drawSizeInfoText(helperContext, { + point: { + x: rectInfo.bottom.x, + y: rectInfo.bottom.y + infoFontSize + }, + rotateCenter: rectInfo.center, + angle: totalAngle, + text: whText, + fontSize: infoFontSize, + lineHeight: infoLineHeight, + color: infoTextColor, + background: infoBackground + }); + + drawPositionInfoText(helperContext, { + point: { + x: rectInfo.topLeft.x, + y: rectInfo.topLeft.y - infoFontSize * 2 + }, + rotateCenter: rectInfo.center, + angle: totalAngle, + text: xyText, + fontSize: infoFontSize, + lineHeight: infoLineHeight, + color: infoTextColor, + background: infoBackground + }); + + drawAngleInfoText(helperContext, { + point: { + x: rectInfo.top.x + infoFontSize, + y: rectInfo.top.y - infoFontSize * 2 + }, + rotateCenter: rectInfo.center, + angle: totalAngle, + text: angleText, + fontSize: infoFontSize, + lineHeight: infoLineHeight, + color: infoTextColor, + background: infoBackground + }); + } + } + } + } + }; +}; diff --git a/packages/core/src/middleware/info/types.ts b/packages/core/src/middleware/info/types.ts new file mode 100644 index 0000000..3f02c7d --- /dev/null +++ b/packages/core/src/middleware/info/types.ts @@ -0,0 +1,4 @@ +import { keySelectedElementList, keyActionType, keyGroupQueue } from '../selector'; +import type { DeepSelectorSharedStorage } from '../selector'; + +export type DeepInfoSharedStorage = Pick; diff --git a/packages/core/src/middleware/ruler/index.ts b/packages/core/src/middleware/ruler/index.ts index 7a95e6e..18ab7e4 100644 --- a/packages/core/src/middleware/ruler/index.ts +++ b/packages/core/src/middleware/ruler/index.ts @@ -1,11 +1,12 @@ import type { BoardMiddleware, CoreEventMap } from '@idraw/types'; import { getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot } from '@idraw/util'; -import { drawRulerBackground, drawXRuler, drawYRuler, calcXRulerScaleList, calcYRulerScaleList, drawUnderGrid } from './util'; +import { drawRulerBackground, drawXRuler, drawYRuler, calcXRulerScaleList, calcYRulerScaleList, drawUnderGrid, drawScrollerSelectedArea } from './util'; +import type { DeepRulerSharedStorage } from './types'; export const middlewareEventRuler = '@middleware/show-ruler'; -export const MiddlewareRuler: BoardMiddleware, CoreEventMap> = (opts) => { - const { boardContent, viewer, eventHub } = opts; +export const MiddlewareRuler: BoardMiddleware = (opts) => { + const { boardContent, viewer, eventHub, calculator } = opts; const { helperContext, underContext } = boardContent; let show: boolean = true; let showGrid: boolean = true; @@ -35,6 +36,8 @@ export const MiddlewareRuler: BoardMiddleware, CoreEventMap> if (show === true) { const viewScaleInfo = getViewScaleInfoFromSnapshot(snapshot); const viewSizeInfo = getViewSizeInfoFromSnapshot(snapshot); + drawScrollerSelectedArea(helperContext, { snapshot, calculator }); + drawRulerBackground(helperContext, { viewScaleInfo, viewSizeInfo }); const xList = calcXRulerScaleList({ viewScaleInfo, viewSizeInfo }); diff --git a/packages/core/src/middleware/ruler/types.ts b/packages/core/src/middleware/ruler/types.ts new file mode 100644 index 0000000..4202f96 --- /dev/null +++ b/packages/core/src/middleware/ruler/types.ts @@ -0,0 +1,4 @@ +import { keySelectedElementList, keyActionType } from '../selector'; +import type { DeepSelectorSharedStorage } from '../selector'; + +export type DeepRulerSharedStorage = Pick; diff --git a/packages/core/src/middleware/ruler/util.ts b/packages/core/src/middleware/ruler/util.ts index b8a2e53..9ec9a6f 100644 --- a/packages/core/src/middleware/ruler/util.ts +++ b/packages/core/src/middleware/ruler/util.ts @@ -1,5 +1,7 @@ -import type { ViewScaleInfo, ViewSizeInfo, ViewContext2D } from '@idraw/types'; -import { formatNumber, rotateByCenter } from '@idraw/util'; +import type { Element, ViewScaleInfo, ViewSizeInfo, ViewContext2D, BoardViewerFrameSnapshot, ViewRectInfo, ViewCalculator } from '@idraw/types'; +import { formatNumber, rotateByCenter, getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot } from '@idraw/util'; +import type { DeepRulerSharedStorage } from './types'; +import { keySelectedElementList, keyActionType } from '../selector'; const rulerSize = 16; const background = '#FFFFFFA8'; @@ -12,6 +14,7 @@ const fontWeight = 100; const gridColor = '#AAAAAA20'; const gridKeyColor = '#AAAAAA40'; const lineSize = 1; +const selectedAreaColor = '#196097'; // const rulerUnit = 10; // const rulerKeyUnit = 100; @@ -235,3 +238,62 @@ export function drawUnderGrid( } // TODO } + +export function drawScrollerSelectedArea(ctx: ViewContext2D, opts: { snapshot: BoardViewerFrameSnapshot; calculator: ViewCalculator }) { + const { snapshot, calculator } = opts; + const { sharedStore } = snapshot; + const selectedElementList = sharedStore[keySelectedElementList]; + const actionType = sharedStore[keyActionType]; + + if (['select', 'drag', 'drag-list', 'drag-list-end'].includes(actionType as string) && selectedElementList.length > 0) { + const viewScaleInfo = getViewScaleInfoFromSnapshot(snapshot); + const viewSizeInfo = getViewSizeInfoFromSnapshot(snapshot); + const rangeRectInfoList: ViewRectInfo[] = []; + const xAreaStartList: number[] = []; + const xAreaEndList: number[] = []; + const yAreaStartList: number[] = []; + const yAreaEndList: number[] = []; + selectedElementList.forEach((elem: Element) => { + const rectInfo = calculator.calcViewRectInfoFromRange(elem.uuid, { + viewScaleInfo, + viewSizeInfo + }); + if (rectInfo) { + rangeRectInfoList.push(rectInfo); + xAreaStartList.push(rectInfo.left.x); + xAreaEndList.push(rectInfo.right.x); + yAreaStartList.push(rectInfo.top.y); + yAreaEndList.push(rectInfo.bottom.y); + } + }); + + if (!(rangeRectInfoList.length > 0)) { + return; + } + + const xAreaStart = Math.min(...xAreaStartList); + const xAreaEnd = Math.max(...xAreaEndList); + const yAreaStart = Math.min(...yAreaStartList); + const yAreaEnd = Math.max(...yAreaEndList); + + ctx.globalAlpha = 1; + + ctx.beginPath(); + ctx.moveTo(xAreaStart, 0); + ctx.lineTo(xAreaEnd, 0); + ctx.lineTo(xAreaEnd, rulerSize); + ctx.lineTo(xAreaStart, rulerSize); + ctx.fillStyle = selectedAreaColor; + ctx.closePath(); + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(0, yAreaStart); + ctx.lineTo(rulerSize, yAreaStart); + ctx.lineTo(rulerSize, yAreaEnd); + ctx.lineTo(0, yAreaEnd); + ctx.fillStyle = selectedAreaColor; + ctx.closePath(); + ctx.fill(); + } +} diff --git a/packages/core/src/middleware/scroller/index.ts b/packages/core/src/middleware/scroller/index.ts index 3670512..2ed6d75 100644 --- a/packages/core/src/middleware/scroller/index.ts +++ b/packages/core/src/middleware/scroller/index.ts @@ -2,8 +2,9 @@ import type { Point, BoardMiddleware, PointWatcherEvent, BoardWatherWheelEvent } import { drawScroller, isPointInScrollThumb } from './util'; // import type { ScrollbarThumbType } from './util'; import { keyXThumbRect, keyYThumbRect, keyPrevPoint, keyActivePoint, keyActiveThumbType } from './config'; +import type { DeepScrollerSharedStorage } from './types'; -export const MiddlewareScroller: BoardMiddleware = (opts) => { +export const MiddlewareScroller: BoardMiddleware = (opts) => { const { viewer, boardContent, sharer } = opts; const { helperContext } = boardContent; sharer.setSharedStorage(keyXThumbRect, null); // null | ElementSize diff --git a/packages/core/src/middleware/scroller/types.ts b/packages/core/src/middleware/scroller/types.ts new file mode 100644 index 0000000..a0059aa --- /dev/null +++ b/packages/core/src/middleware/scroller/types.ts @@ -0,0 +1,10 @@ +import type { Point, ElementSize } from '@idraw/types'; +import { keyXThumbRect, keyYThumbRect, keyPrevPoint, keyActivePoint, keyActiveThumbType } from './config'; + +export type DeepScrollerSharedStorage = { + [keyXThumbRect]: null | ElementSize; + [keyYThumbRect]: null | ElementSize; + [keyPrevPoint]: null | Point; + [keyActivePoint]: null | Point; + [keyActiveThumbType]: null | 'X' | 'Y'; +}; diff --git a/packages/core/src/middleware/scroller/util.ts b/packages/core/src/middleware/scroller/util.ts index a72852a..8bbc870 100644 --- a/packages/core/src/middleware/scroller/util.ts +++ b/packages/core/src/middleware/scroller/util.ts @@ -4,7 +4,7 @@ import { keyActivePoint, keyActiveThumbType, keyPrevPoint, keyXThumbRect, keyYTh const minScrollerWidth = 12; const scrollerLineWidth = 16; -const scrollerThumbAlpha = 0.36; +const scrollerThumbAlpha = 0.3; export type ScrollbarThumbType = 'X' | 'Y'; diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index 0e84d86..a40f19f 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -71,6 +71,9 @@ import { import { calcReferenceInfo } from './reference'; import { middlewareEventTextEdit } from '../text-editor'; +export { keySelectedElementList, keyActionType, keyResizeType, keyGroupQueue }; +export type { DeepSelectorSharedStorage }; + export const middlewareEventSelect: string = '@middleware/select'; export const middlewareEventSelectClear: string = '@middleware/select-clear'; @@ -445,6 +448,7 @@ export const MiddlewareSelector: BoardMiddleware, { - w: calculator.toGridNum(resizedElemSize.w), - h: calculator.toGridNum(resizedElemSize.h) + w: calculator.toGridNum(resizedElemSize.w, calcOpts), + h: calculator.toGridNum(resizedElemSize.h, calcOpts) }); } else { - elems[0].w = calculator.toGridNum(resizedElemSize.w); - elems[0].h = calculator.toGridNum(resizedElemSize.h); + elems[0].w = calculator.toGridNum(resizedElemSize.w, calcOpts); + elems[0].h = calculator.toGridNum(resizedElemSize.h, calcOpts); } } diff --git a/packages/idraw/src/config.ts b/packages/idraw/src/config.ts index fac8b6b..e3cb5d7 100644 --- a/packages/idraw/src/config.ts +++ b/packages/idraw/src/config.ts @@ -14,7 +14,8 @@ export function getDefaultStorage(): IDrawStorage { enableScroll: false, enableSelect: false, enableTextEdit: false, - enableDrag: false + enableDrag: false, + enableInfo: false }; return storage; } diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index 1311da4..be2f116 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -58,11 +58,12 @@ export class iDraw { #setFeature(feat: IDrawFeature, status: boolean) { const store = this.#store; - if (['ruler', 'scroll', 'scale'].includes(feat)) { + if (['ruler', 'scroll', 'scale', 'info'].includes(feat)) { const map: Record> = { ruler: 'enableRuler', scroll: 'enableScroll', - scale: 'enableScale' + scale: 'enableScale', + info: 'enableInfo' }; store.set(map[feat], !!status); runMiddlewares(this.#core, store); diff --git a/packages/idraw/src/mode.ts b/packages/idraw/src/mode.ts index 545390c..f3aa715 100644 --- a/packages/idraw/src/mode.ts +++ b/packages/idraw/src/mode.ts @@ -1,6 +1,15 @@ import type { IDrawMode, IDrawStorage } from '@idraw/types'; import { Store } from '@idraw/util'; -import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler, MiddlewareTextEditor, MiddlewareDragger } from '@idraw/core'; +import { + Core, + MiddlewareSelector, + MiddlewareScroller, + MiddlewareScaler, + MiddlewareRuler, + MiddlewareTextEditor, + MiddlewareDragger, + MiddlewareInfo +} from '@idraw/core'; import { IDrawEvent } from './event'; function isValidMode(mode: string | IDrawMode) { @@ -8,7 +17,7 @@ function isValidMode(mode: string | IDrawMode) { } export function runMiddlewares(core: Core, store: Store) { - const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag } = store.getSnapshot(); + const { enableRuler, enableScale, enableScroll, enableSelect, enableTextEdit, enableDrag, enableInfo } = store.getSnapshot(); if (enableScroll === true) { core.use(MiddlewareScroller); } else if (enableScroll === false) { @@ -44,6 +53,12 @@ export function runMiddlewares(core: Core, store: Store, store: Store) { @@ -53,6 +68,7 @@ export function changeMode(mode: IDrawMode, core: Core, store: Store let enableTextEdit: boolean = false; let enableDrag: boolean = false; let enableRuler: boolean = false; + const enableInfo: boolean = true; let innerMode: IDrawMode = 'select'; store.set('mode', innerMode); @@ -92,6 +108,7 @@ export function changeMode(mode: IDrawMode, core: Core, store: Store store.set('enableTextEdit', enableTextEdit); store.set('enableDrag', enableDrag); store.set('enableRuler', enableRuler); + store.set('enableInfo', enableInfo); runMiddlewares(core, store); } diff --git a/packages/renderer/src/draw/box.ts b/packages/renderer/src/draw/box.ts index 6c8f26b..6a2053e 100644 --- a/packages/renderer/src/draw/box.ts +++ b/packages/renderer/src/draw/box.ts @@ -14,9 +14,9 @@ export function getOpacity(elem: Element): number { export function drawBox( ctx: ViewContext2D, - viewElem: Element, + viewElem: Element, opts: { - originElem: Element; + originElem: Element; calcElemSize: ElementSize; pattern?: string | CanvasPattern | null; renderContent: () => void; @@ -88,7 +88,7 @@ function drawClipPath( } } -function drawBoxBackground( +export function drawBoxBackground( ctx: ViewContext2D, viewElem: Element, opts: { pattern?: string | CanvasPattern | null; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } @@ -149,7 +149,7 @@ function drawBoxBackground( } } -function drawBoxBorder(ctx: ViewContext2D, viewElem: Element, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): void { +export function drawBoxBorder(ctx: ViewContext2D, viewElem: Element, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): void { if (viewElem.detail.borderWidth === 0) { return; } diff --git a/packages/renderer/src/draw/elements.ts b/packages/renderer/src/draw/elements.ts index 2a8f3c2..287a849 100644 --- a/packages/renderer/src/draw/elements.ts +++ b/packages/renderer/src/draw/elements.ts @@ -19,7 +19,7 @@ export function drawElementList(ctx: ViewContext2D, data: Data, opts: RendererDr } }; if (opts.forceDrawAll !== true) { - if (!opts.calculator?.isElementInView(elem, opts.viewScaleInfo, opts.viewSizeInfo)) { + if (!opts.calculator?.needRender(elem)) { continue; } } diff --git a/packages/renderer/src/draw/group.ts b/packages/renderer/src/draw/group.ts index 197b5db..718a13e 100644 --- a/packages/renderer/src/draw/group.ts +++ b/packages/renderer/src/draw/group.ts @@ -126,7 +126,7 @@ export function drawGroup(ctx: ViewContext2D, elem: Element<'group'>, opts: Rend } }; if (opts.forceDrawAll !== true) { - if (!calculator?.isElementInView(child, opts.viewScaleInfo, opts.viewSizeInfo)) { + if (!calculator?.needRender(child)) { continue; } } diff --git a/packages/renderer/src/draw/index.ts b/packages/renderer/src/draw/index.ts index e4cf242..fd5228f 100644 --- a/packages/renderer/src/draw/index.ts +++ b/packages/renderer/src/draw/index.ts @@ -5,4 +5,4 @@ export { drawSVG } from './svg'; export { drawHTML } from './html'; export { drawText } from './text'; export { drawElementList } from './elements'; -export { drawUnderlay } from './underlay'; +export { drawLayout } from './layout'; diff --git a/packages/renderer/src/draw/layout.ts b/packages/renderer/src/draw/layout.ts new file mode 100644 index 0000000..6f23736 --- /dev/null +++ b/packages/renderer/src/draw/layout.ts @@ -0,0 +1,51 @@ +import type { RendererDrawElementOptions, ViewContext2D, DataLayout, Element } from '@idraw/types'; +import { calcViewElementSize, calcViewBoxSize } from '@idraw/util'; +import { drawBoxShadow, drawBoxBackground, drawBoxBorder } from './box'; + +export function drawLayout(ctx: ViewContext2D, layout: DataLayout, opts: RendererDrawElementOptions, renderContent: (ctx: ViewContext2D) => void) { + const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts; + const elem: Element = { uuid: 'layout', type: 'group', x: 0, y: 0, ...layout }; + const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem; + const angle = 0; + const viewElem: Element = { ...elem, ...{ x, y, w, h, angle } } as Element; + ctx.globalAlpha = 1; + drawBoxShadow(ctx, viewElem, { + viewScaleInfo, + viewSizeInfo, + renderContent: () => { + drawBoxBackground(ctx, viewElem, { viewScaleInfo, viewSizeInfo }); + } + }); + + if (layout.detail.overflow === 'hidden') { + const { viewScaleInfo, viewSizeInfo } = opts; + const elem: Element<'group'> = { uuid: 'layout', type: 'group', x: 0, y: 0, ...layout } as Element<'group'>; + const viewElemSize = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem; + const viewElem = { ...elem, ...viewElemSize }; + const { x, y, w, h, radiusList } = calcViewBoxSize(viewElem, { + viewScaleInfo, + viewSizeInfo + }); + ctx.save(); + + ctx.fillStyle = 'transparent'; + ctx.beginPath(); + ctx.moveTo(x + radiusList[0], y); + ctx.arcTo(x + w, y, x + w, y + h, radiusList[1]); + ctx.arcTo(x + w, y + h, x, y + h, radiusList[2]); + ctx.arcTo(x, y + h, x, y, radiusList[3]); + ctx.arcTo(x, y, x + w, y, radiusList[0]); + ctx.closePath(); + ctx.fill(); + ctx.clip(); + } + + renderContent(ctx); + + if (layout.detail.overflow === 'hidden') { + ctx.restore(); + } + + drawBoxBorder(ctx, viewElem, { viewScaleInfo, viewSizeInfo }); + ctx.globalAlpha = parentOpacity; +} diff --git a/packages/renderer/src/draw/underlay.ts b/packages/renderer/src/draw/underlay.ts deleted file mode 100644 index 85f5289..0000000 --- a/packages/renderer/src/draw/underlay.ts +++ /dev/null @@ -1,27 +0,0 @@ -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 { viewScaleInfo, viewSizeInfo, parentOpacity } = opts; - const elem = { uuid: 'underlay', ...underlay }; - const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem; - const angle = 0; - const viewElem = { ...elem, ...{ x, y, w, h, angle } }; - drawBoxShadow(ctx, viewElem, { - viewScaleInfo, - viewSizeInfo, - renderContent: () => { - drawBox(ctx, viewElem, { - originElem: elem, - calcElemSize: { x, y, w, h, angle }, - viewScaleInfo, - viewSizeInfo, - parentOpacity, - renderContent: () => { - // empty - } - }); - } - }); -} diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index 0e94ca7..a7d8a7c 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -1,6 +1,6 @@ import { EventEmitter } from '@idraw/util'; -import type { LoadItemMap } from '@idraw/types'; -import { drawElementList, drawUnderlay } from './draw/index'; +import type { DataLayout, LoadItemMap } from '@idraw/types'; +import { drawElementList, drawLayout } from './draw/index'; import { Loader } from './loader'; import type { Data, BoardRenderer, RendererOptions, RendererEventMap, RendererDrawOptions } from '@idraw/types'; @@ -54,23 +54,30 @@ export class Renderer extends EventEmitter implements BoardRen w: opts.viewSizeInfo.width, h: opts.viewSizeInfo.height }; - if (data.underlay) { - drawUnderlay(viewContext, data.underlay, { - loader, - calculator, - parentElementSize, - parentOpacity: 1, - ...opts - }); - } - drawElementList(viewContext, data, { + // if (data.underlay) { + // drawUnderlay(viewContext, data.underlay, { + // loader, + // calculator, + // parentElementSize, + // parentOpacity: 1, + // ...opts + // }); + // } + const drawOpts = { loader, calculator, parentElementSize, elementAssets: data.assets, parentOpacity: 1, ...opts - }); + }; + if (data.layout) { + drawLayout(viewContext, data.layout as DataLayout, drawOpts, () => { + drawElementList(viewContext, data, drawOpts); + }); + } else { + drawElementList(viewContext, data, drawOpts); + } } scale(num: number) { diff --git a/packages/types/src/lib/data.ts b/packages/types/src/lib/data.ts index 4521132..009b956 100644 --- a/packages/types/src/lib/data.ts +++ b/packages/types/src/lib/data.ts @@ -1,11 +1,12 @@ -import type { Element, ElementType, ElementAssets } from './element'; - -export type DataUnderlay = Omit, 'uuid' | 'angle'>; // TODO color background +import type { Element, ElementType, ElementAssets, ElementSize, ElementGroupDetail } from './element'; +export type DataLayout = Pick & { + detail: Omit; +}; export interface Data = Record> { elements: Element[]; assets?: ElementAssets; - underlay?: DataUnderlay; // Rect or Color + layout?: DataLayout; } export type Matrix = [ diff --git a/packages/types/src/lib/idraw.ts b/packages/types/src/lib/idraw.ts index 0538767..2299adb 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' | 'scroll' | 'scale'; // TODO other feature +export type IDrawFeature = 'ruler' | 'scroll' | 'scale' | 'info'; // TODO other feature export interface IDrawSettings { mode?: IDrawMode; @@ -18,4 +18,5 @@ export interface IDrawStorage { enableSelect: boolean; enableTextEdit: boolean; enableDrag: boolean; + enableInfo: boolean; } diff --git a/packages/types/src/lib/view.ts b/packages/types/src/lib/view.ts index 3a00698..4a2e294 100644 --- a/packages/types/src/lib/view.ts +++ b/packages/types/src/lib/view.ts @@ -43,8 +43,11 @@ export interface ViewCalculatorStorage { } export interface ViewCalculator { - isElementInView(elem: Element, viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean; + /** + * @deprecated + */ isPointInElement(p: Point, elem: Element, viewScaleInfo: ViewScaleInfo, viewSize: ViewSizeInfo): boolean; + needRender(elem: Element): boolean; getPointElement( p: Point, opts: { data: Data; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; groupQueue?: Element<'group'>[] } @@ -82,7 +85,7 @@ export interface ViewCalculator { } ): void; - toGridNum(num: number): number; + toGridNum(num: number, opts?: { ignore?: boolean }): number; } export type ViewRectVertexes = [PointSize, PointSize, PointSize, PointSize]; diff --git a/packages/util/src/lib/view-calc.ts b/packages/util/src/lib/view-calc.ts index ae160b0..fd10909 100644 --- a/packages/util/src/lib/view-calc.ts +++ b/packages/util/src/lib/view-calc.ts @@ -119,6 +119,9 @@ export function calcViewVertexes(vertexes: ViewRectVertexes, opts: { viewScaleIn ]; } +/** + * @deprecated + */ export function isViewPointInElement( p: Point, opts: { context2d: ViewContext2D; element: ElementSize; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } @@ -224,6 +227,9 @@ export function getViewPointAtElement( return result; } +/** + * @deprecated + */ export function isElementInView(elem: ElementSize, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): boolean { const { viewSizeInfo, viewScaleInfo } = opts; const { width, height } = viewSizeInfo; @@ -434,7 +440,7 @@ export function calcElementViewRectInfoMap( return { originRectInfo, - viewRectInfo, + // viewRectInfo, rangeRectInfo }; } diff --git a/packages/util/src/lib/view-content.ts b/packages/util/src/lib/view-content.ts index bdbad50..3f764b6 100644 --- a/packages/util/src/lib/view-content.ts +++ b/packages/util/src/lib/view-content.ts @@ -18,39 +18,45 @@ export function calcViewCenterContent(data: Data, opts: { viewSizeInfo: ViewSize let contentY: number = data?.elements?.[0]?.y || 0; let contentW: number = data?.elements?.[0]?.w || 0; let contentH: number = data?.elements?.[0]?.h || 0; - const { width, height } = opts.viewSizeInfo; - data.elements.forEach((elem: Element) => { - const elemSize: ElementSize = { - x: elem.x, - y: elem.y, - w: elem.w, - h: elem.h, - angle: elem.angle - }; - if (elemSize.angle && (elemSize.angle > 0 || elemSize.angle < 0)) { - const ves = rotateElementVertexes(elemSize); - if (ves.length === 4) { - const xList = [ves[0].x, ves[1].x, ves[2].x, ves[3].x]; - const yList = [ves[0].y, ves[1].y, ves[2].y, ves[3].y]; - elemSize.x = Math.min(...xList); - elemSize.y = Math.min(...yList); - elemSize.w = Math.abs(Math.max(...xList) - Math.min(...xList)); - elemSize.h = Math.abs(Math.max(...yList) - Math.min(...yList)); + if (data.layout && data.layout?.detail?.overflow === 'hidden') { + contentX = 0; + contentY = 0; + contentW = data.layout.w || 0; + contentH = data.layout.h || 0; + } else { + data.elements.forEach((elem: Element) => { + const elemSize: ElementSize = { + x: elem.x, + y: elem.y, + w: elem.w, + h: elem.h, + angle: elem.angle + }; + if (elemSize.angle && (elemSize.angle > 0 || elemSize.angle < 0)) { + const ves = rotateElementVertexes(elemSize); + if (ves.length === 4) { + const xList = [ves[0].x, ves[1].x, ves[2].x, ves[3].x]; + const yList = [ves[0].y, ves[1].y, ves[2].y, ves[3].y]; + elemSize.x = Math.min(...xList); + elemSize.y = Math.min(...yList); + elemSize.w = Math.abs(Math.max(...xList) - Math.min(...xList)); + elemSize.h = Math.abs(Math.max(...yList) - Math.min(...yList)); + } } - } - const areaStartX = Math.min(elemSize.x, contentX); - const areaStartY = Math.min(elemSize.y, contentY); + const areaStartX = Math.min(elemSize.x, contentX); + const areaStartY = Math.min(elemSize.y, contentY); - const areaEndX = Math.max(elemSize.x + elemSize.w, contentX + contentW); - const areaEndY = Math.max(elemSize.y + elemSize.h, contentY + contentH); + const areaEndX = Math.max(elemSize.x + elemSize.w, contentX + contentW); + const areaEndY = Math.max(elemSize.y + elemSize.h, contentY + contentH); - contentX = areaStartX; - contentY = areaStartY; - contentW = Math.abs(areaEndX - areaStartX); - contentH = Math.abs(areaEndY - areaStartY); - }); + contentX = areaStartX; + contentY = areaStartY; + contentW = Math.abs(areaEndX - areaStartX); + contentH = Math.abs(areaEndY - areaStartY); + }); + } if (contentW > 0 && contentH > 0) { const scaleW = formatNumber(width / contentW, { decimalPlaces: 4 });