From c0de3034ec4a741baa8534eb04e71436a3d89bf4 Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 30 Mar 2024 10:22:21 +0800 Subject: [PATCH] feat: add middleware of layout selector --- packages/core/src/config.ts | 1 + packages/core/src/index.ts | 2 + .../src/middleware/layout-selector/config.ts | 8 + .../src/middleware/layout-selector/index.ts | 261 ++++++++++++++++++ .../src/middleware/layout-selector/types.ts | 15 + .../src/middleware/layout-selector/util.ts | 111 ++++++++ .../core/src/middleware/selector/index.ts | 13 +- packages/idraw/src/event.ts | 18 +- packages/idraw/src/idraw.ts | 2 +- packages/idraw/src/mode.ts | 3 + packages/renderer/src/draw/layout.ts | 4 +- packages/renderer/src/draw/text.ts | 13 +- packages/types/src/lib/config.ts | 2 +- packages/types/src/lib/controller.ts | 2 + packages/types/src/lib/data.ts | 17 +- packages/util/src/index.ts | 6 +- packages/util/src/lib/config.ts | 2 +- packages/util/src/lib/controller.ts | 118 +++++++- packages/util/src/lib/view-calc.ts | 44 ++- packages/util/src/lib/view-content.ts | 11 + 20 files changed, 626 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/config.ts create mode 100644 packages/core/src/middleware/layout-selector/config.ts create mode 100644 packages/core/src/middleware/layout-selector/index.ts create mode 100644 packages/core/src/middleware/layout-selector/types.ts create mode 100644 packages/core/src/middleware/layout-selector/util.ts diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts new file mode 100644 index 000000000..88ca53dde --- /dev/null +++ b/packages/core/src/config.ts @@ -0,0 +1 @@ +export const eventChange = 'change'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ea6668e25..9524db849 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -2,6 +2,7 @@ import type { Data, PointSize, CoreOptions, BoardMiddleware, ViewSizeInfo, CoreE import { Board } from '@idraw/board'; import { createBoardContent, validateElements } from '@idraw/util'; import { Cursor } from './lib/cursor'; +export { eventChange } from './config'; // export { MiddlewareSelector } from './middleware/selector'; export { MiddlewareSelector, middlewareEventSelect, middlewareEventSelectClear } from './middleware/selector'; @@ -11,6 +12,7 @@ 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 { MiddlewareLayoutSelector } from './middleware/layout-selector'; export class Core { #board: Board; diff --git a/packages/core/src/middleware/layout-selector/config.ts b/packages/core/src/middleware/layout-selector/config.ts new file mode 100644 index 000000000..b5b2af27d --- /dev/null +++ b/packages/core/src/middleware/layout-selector/config.ts @@ -0,0 +1,8 @@ +export const key = 'LAYOUT_SELECT'; +// export const keyHoverElement = Symbol(`${key}_hoverElementSize`); +export const keyLayoutActionType = Symbol(`${key}_layoutActionType`); // 'hover' | 'resize' | null = null; +export const keyLayoutControlType = Symbol(`${key}_layoutControlType`); // ControlType | null; +export const keyLayoutController = Symbol(`${key}_layoutController`); // ElementSizeController | null = null; + +export const selectColor = '#1973ba'; +export const disableColor = '#5b5959b5'; diff --git a/packages/core/src/middleware/layout-selector/index.ts b/packages/core/src/middleware/layout-selector/index.ts new file mode 100644 index 000000000..5ca4432de --- /dev/null +++ b/packages/core/src/middleware/layout-selector/index.ts @@ -0,0 +1,261 @@ +import type { BoardMiddleware, ElementSize, Point } from '@idraw/types'; +import { calcLayoutSizeController, isViewPointInVertexes, getViewScaleInfoFromSnapshot } from '@idraw/util'; +import type { LayoutSelectorSharedStorage, ControlType } from './types'; +import { keyLayoutActionType, keyLayoutController, keyLayoutControlType } from './config'; +import { keyActionType as keyElementActionType, middlewareEventSelectClear } from '../selector'; +import { drawLayoutController } from './util'; +import { eventChange } from '../../config'; + +export const MiddlewareLayoutSelector: BoardMiddleware = (opts) => { + const { sharer, boardContent, calculator, viewer, eventHub } = opts; + const { helperContext } = boardContent; + + let prevPoint: Point | null = null; + + const clear = () => { + prevPoint = null; + sharer.setSharedStorage(keyLayoutActionType, null); + sharer.setSharedStorage(keyLayoutControlType, null); + sharer.setSharedStorage(keyLayoutController, null); + }; + + const isInElementAction = () => { + const elementType = sharer.getSharedStorage(keyElementActionType); + if (elementType) { + return true; + } + return false; + }; + + const isDisbaledControl = (controlType: ControlType) => { + const data = sharer.getActiveStorage('data'); + if (data?.layout?.operations) { + const operations = data.layout.operations; + if (controlType === 'left' && operations.disableLeft === true) { + return true; + } + if (controlType === 'top' && operations.disableTop === true) { + return true; + } + if (controlType === 'right' && operations.disableRight === true) { + return true; + } + if (controlType === 'bottom' && operations.disableBottom === true) { + return true; + } + if (controlType === 'top-left' && operations.disableTopLeft === true) { + return true; + } + if (controlType === 'top-right' && operations.disableTopRight === true) { + return true; + } + if (controlType === 'bottom-left' && operations.disableBottomLeft === true) { + return true; + } + if (controlType === 'bottom-right' && operations.disableBottomRight === true) { + return true; + } + } + return false; + }; + + const getLayoutSize = () => { + const data = sharer.getActiveStorage('data'); + if (data?.layout) { + const { x, y, w, h } = data.layout; + return { x, y, w, h }; + } + return null; + }; + + const resetController = () => { + const viewScaleInfo = sharer.getActiveViewScaleInfo(); + const size: ElementSize | null = getLayoutSize(); + if (size) { + const controller = calcLayoutSizeController(size, { viewScaleInfo, controllerSize: 10 }); + sharer.setSharedStorage(keyLayoutController, controller); + } else { + sharer.setSharedStorage(keyLayoutController, null); + } + }; + + const resetControlType = (e?: { point: Point }) => { + const data = sharer.getActiveStorage('data'); + const controller = sharer.getSharedStorage(keyLayoutController); + if (controller && data?.layout && e?.point) { + // sharer.setSharedStorage(keyLayoutControlType, null); + let layoutControlType: ControlType | null = null; + if (controller) { + const { topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left } = controller; + const list = [topLeft, top, topRight, right, bottomRight, bottom, bottomLeft, left]; + for (let i = 0; i < list.length; i++) { + const item = list[i]; + if (isViewPointInVertexes(e.point, item.vertexes)) { + layoutControlType = `${item.type}` as ControlType; + break; + } + } + if (layoutControlType) { + sharer.setSharedStorage(keyLayoutControlType, layoutControlType); + eventHub.trigger(middlewareEventSelectClear, {}); + return layoutControlType; + } + } + } + return null; + }; + + return { + name: '@middleware/layout-selector', + use: () => { + clear(); + resetController(); + }, + hover: (e) => { + if (isInElementAction()) { + return; + } + const prevLayoutActionType = sharer.getSharedStorage(keyLayoutActionType); + + const data = sharer.getActiveStorage('data'); + if (data?.layout && prevLayoutActionType !== 'resize') { + resetController(); + const layoutControlType = resetControlType(e); + if (layoutControlType) { + sharer.setSharedStorage(keyLayoutActionType, 'hover'); + if (!isDisbaledControl(layoutControlType)) { + eventHub.trigger('cursor', { + type: `resize-${layoutControlType}`, + groupQueue: [], + element: getLayoutSize() + }); + } + + viewer.drawFrame(); + } else { + sharer.setSharedStorage(keyLayoutActionType, null); + } + } + if (['hover', 'resize'].includes(sharer.getSharedStorage(keyLayoutActionType) as string)) { + return false; + } + if (prevLayoutActionType === 'hover' && !sharer.getSharedStorage(keyLayoutActionType)) { + viewer.drawFrame(); + } + }, + pointStart: (e) => { + if (isInElementAction()) { + return; + } + resetController(); + const layoutControlType = resetControlType(e); + prevPoint = e.point; + if (layoutControlType) { + if (isDisbaledControl(layoutControlType)) { + return; + } + sharer.setSharedStorage(keyLayoutActionType, 'resize'); + return false; + } + const layoutActionType = sharer.getSharedStorage(keyLayoutActionType); + if (['hover', 'resize'].includes(layoutActionType as string)) { + return false; + } + }, + pointMove: (e) => { + if (isInElementAction()) { + return; + } + const layoutActionType = sharer.getSharedStorage(keyLayoutActionType); + const layoutControlType = sharer.getSharedStorage(keyLayoutControlType); + const data = sharer.getActiveStorage('data'); + if (layoutControlType && isDisbaledControl(layoutControlType)) { + return; + } + + if (layoutActionType === 'resize' && layoutControlType && data?.layout) { + if (prevPoint) { + const scale = sharer.getActiveStorage('scale'); + const moveX = (e.point.x - prevPoint.x) / scale; + const moveY = (e.point.y - prevPoint.y) / scale; + const { x, y, w, h } = data.layout; + + if (layoutControlType === 'top') { + data.layout.y = calculator.toGridNum(y + moveY); + data.layout.h = calculator.toGridNum(h - moveY); + } else if (layoutControlType === 'right') { + data.layout.w = calculator.toGridNum(w + moveX); + } else if (layoutControlType === 'bottom') { + data.layout.h = calculator.toGridNum(h + moveY); + } else if (layoutControlType === 'left') { + data.layout.x = calculator.toGridNum(x + moveX); + data.layout.w = calculator.toGridNum(w - moveX); + } else if (layoutControlType === 'top-left') { + data.layout.x = calculator.toGridNum(x + moveX); + data.layout.y = calculator.toGridNum(y + moveY); + data.layout.w = calculator.toGridNum(w - moveX); + data.layout.h = calculator.toGridNum(h - moveY); + } else if (layoutControlType === 'top-right') { + data.layout.y = calculator.toGridNum(y + moveY); + data.layout.w = calculator.toGridNum(w + moveX); + data.layout.h = calculator.toGridNum(h - moveY); + } else if (layoutControlType === 'bottom-right') { + data.layout.w = calculator.toGridNum(w + moveX); + data.layout.h = calculator.toGridNum(h + moveY); + } else if (layoutControlType === 'bottom-left') { + data.layout.x = calculator.toGridNum(x + moveX); + data.layout.w = calculator.toGridNum(w - moveX); + data.layout.h = calculator.toGridNum(h + moveY); + } + } + prevPoint = e.point; + resetController(); + viewer.drawFrame(); + + return false; + } + + if (['hover', 'resize'].includes(layoutActionType as string)) { + return false; + } + }, + pointEnd: () => { + const layoutActionType = sharer.getSharedStorage(keyLayoutActionType); + const layoutControlType = sharer.getSharedStorage(keyLayoutControlType); + const data = sharer.getActiveStorage('data'); + if (data && layoutActionType === 'resize' && layoutControlType && !isDisbaledControl(layoutControlType)) { + eventHub.trigger(eventChange, { + type: 'changeLayout', + data + }); + } + + clear(); + }, + beforeDrawFrame: ({ snapshot }) => { + const { sharedStore, activeStore } = snapshot; + const layoutActionType = sharedStore[keyLayoutActionType]; + const layoutControlType = sharedStore[keyLayoutControlType]; + + if (activeStore.data?.layout && layoutActionType && layoutControlType) { + if (['hover', 'resize'].includes(layoutActionType)) { + const viewScaleInfo = getViewScaleInfoFromSnapshot(snapshot); + const { x, y, w, h } = activeStore.data.layout; + const size = { x, y, w, h }; + const controller = calcLayoutSizeController(size, { viewScaleInfo, controllerSize: 10 }); + + drawLayoutController(helperContext, { controller, operations: activeStore.data.layout.operations || {} }); + } + } + }, + scrollX: () => { + clear(); + }, + scrollY: () => { + clear(); + }, + wheelScale: () => { + clear(); + } + }; +}; diff --git a/packages/core/src/middleware/layout-selector/types.ts b/packages/core/src/middleware/layout-selector/types.ts new file mode 100644 index 000000000..7247f90a7 --- /dev/null +++ b/packages/core/src/middleware/layout-selector/types.ts @@ -0,0 +1,15 @@ +import type { LayoutSizeController } from '@idraw/types'; +import { keyLayoutActionType, keyLayoutControlType, keyLayoutController } from './config'; +import { keyActionType as keyElementActionType } from '../selector'; +import type { ActionType as ElementActionType } from '../selector'; + +export type ActionType = 'hover' | 'resize' | null; + +export type ControlType = 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + +export type LayoutSelectorSharedStorage = { + [keyLayoutActionType]: ActionType | null; + [keyLayoutControlType]: ControlType | null; + [keyLayoutController]: LayoutSizeController | null; + [keyElementActionType]: ElementActionType | null; +}; diff --git a/packages/core/src/middleware/layout-selector/util.ts b/packages/core/src/middleware/layout-selector/util.ts new file mode 100644 index 000000000..500376a3d --- /dev/null +++ b/packages/core/src/middleware/layout-selector/util.ts @@ -0,0 +1,111 @@ +import type { ViewContext2D, LayoutSizeController, DataLayout, ViewRectVertexes, PointSize } from '@idraw/types'; +import { selectColor, disableColor } from './config'; + +function drawControllerBox(ctx: ViewContext2D, boxVertexes: ViewRectVertexes) { + ctx.setLineDash([]); + ctx.fillStyle = '#FFFFFF'; + ctx.beginPath(); + ctx.moveTo(boxVertexes[0].x, boxVertexes[0].y); + ctx.lineTo(boxVertexes[1].x, boxVertexes[1].y); + ctx.lineTo(boxVertexes[2].x, boxVertexes[2].y); + ctx.lineTo(boxVertexes[3].x, boxVertexes[3].y); + ctx.closePath(); + ctx.fill(); + + ctx.strokeStyle = selectColor; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(boxVertexes[0].x, boxVertexes[0].y); + ctx.lineTo(boxVertexes[1].x, boxVertexes[1].y); + ctx.lineTo(boxVertexes[2].x, boxVertexes[2].y); + ctx.lineTo(boxVertexes[3].x, boxVertexes[3].y); + ctx.closePath(); + ctx.stroke(); +} + +function drawControllerCross(ctx: ViewContext2D, opts: { vertexes: ViewRectVertexes; strokeStyle: string; lineWidth: number }) { + const { vertexes, strokeStyle, lineWidth } = opts; + + ctx.setLineDash([]); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + + ctx.beginPath(); + ctx.moveTo(vertexes[0].x, vertexes[0].y); + ctx.lineTo(vertexes[2].x, vertexes[2].y); + ctx.closePath(); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(vertexes[1].x, vertexes[1].y); + ctx.lineTo(vertexes[3].x, vertexes[3].y); + ctx.closePath(); + ctx.stroke(); +} + +function drawControllerLine(ctx: ViewContext2D, opts: { start: PointSize; end: PointSize; centerVertexes: ViewRectVertexes; disabled: boolean }) { + const { start, end, centerVertexes, disabled } = opts; + const lineWidth = disabled === true ? 1 : 2; + const strokeStyle = disabled === true ? disableColor : selectColor; + ctx.setLineDash([]); + ctx.strokeStyle = strokeStyle; + ctx.lineWidth = lineWidth; + ctx.beginPath(); + ctx.moveTo(start.x, start.y); + ctx.lineTo(end.x, end.y); + ctx.closePath(); + ctx.stroke(); + + if (disabled === true) { + drawControllerCross(ctx, { + vertexes: centerVertexes, + lineWidth, + strokeStyle + }); + } +} + +export function drawLayoutController( + ctx: ViewContext2D, + opts: { + controller: LayoutSizeController; + operations: DataLayout['operations']; + } +) { + // TODO + const { controller, operations } = opts; + const { topLeft, topRight, bottomLeft, bottomRight, topMiddle, rightMiddle, bottomMiddle, leftMiddle } = controller; + + drawControllerLine(ctx, { start: topLeft.center, end: topRight.center, centerVertexes: topMiddle.vertexes, disabled: !!operations?.disableTop }); + drawControllerLine(ctx, { start: topRight.center, end: bottomRight.center, centerVertexes: rightMiddle.vertexes, disabled: !!operations?.disableRight }); + drawControllerLine(ctx, { start: bottomRight.center, end: bottomLeft.center, centerVertexes: bottomMiddle.vertexes, disabled: !!operations?.disableBottom }); + drawControllerLine(ctx, { start: bottomLeft.center, end: topLeft.center, centerVertexes: leftMiddle.vertexes, disabled: !!operations?.disableLeft }); + + const disabledOpts = { + lineWidth: 1, + strokeStyle: disableColor + }; + if (operations?.disableTopLeft === true) { + drawControllerCross(ctx, { vertexes: topLeft.vertexes, ...disabledOpts }); + } else { + drawControllerBox(ctx, topLeft.vertexes); + } + + if (operations?.disableTopRight === true) { + drawControllerCross(ctx, { vertexes: topRight.vertexes, ...disabledOpts }); + } else { + drawControllerBox(ctx, topRight.vertexes); + } + + if (operations?.disableBottomRight === true) { + drawControllerCross(ctx, { vertexes: bottomRight.vertexes, ...disabledOpts }); + } else { + drawControllerBox(ctx, bottomRight.vertexes); + } + + if (operations?.disableBottomLeft === true) { + drawControllerCross(ctx, { vertexes: bottomLeft.vertexes, ...disabledOpts }); + } else { + drawControllerBox(ctx, bottomLeft.vertexes); + } +} diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index a40f19f56..0648448a7 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -70,9 +70,10 @@ import { } from './config'; import { calcReferenceInfo } from './reference'; import { middlewareEventTextEdit } from '../text-editor'; +import { eventChange } from '../../config'; export { keySelectedElementList, keyActionType, keyResizeType, keyGroupQueue }; -export type { DeepSelectorSharedStorage }; +export type { DeepSelectorSharedStorage, ActionType }; export const middlewareEventSelect: string = '@middleware/select'; @@ -292,6 +293,11 @@ export const MiddlewareSelector: BoardMiddleware 0) { + if (data?.layout || (Array.isArray(data?.elements) && data?.elements.length > 0)) { const result = calcViewCenterContent(data, { viewSizeInfo }); this.setViewScale(result); } diff --git a/packages/idraw/src/mode.ts b/packages/idraw/src/mode.ts index f3aa715cb..4cbf2c96e 100644 --- a/packages/idraw/src/mode.ts +++ b/packages/idraw/src/mode.ts @@ -2,6 +2,7 @@ import type { IDrawMode, IDrawStorage } from '@idraw/types'; import { Store } from '@idraw/util'; import { Core, + MiddlewareLayoutSelector, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, @@ -25,8 +26,10 @@ export function runMiddlewares(core: Core, store: Store void) { const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts; - const elem: Element = { uuid: 'layout', type: 'group', x: 0, y: 0, ...layout }; + const elem: Element = { uuid: 'layout', type: 'group', ...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; @@ -19,7 +19,7 @@ export function drawLayout(ctx: ViewContext2D, layout: DataLayout, opts: Rendere 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 elem: Element<'group'> = { uuid: 'layout', type: 'group', ...layout } as Element<'group'>; const viewElemSize = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem; const viewElem = { ...elem, ...viewElemSize }; const { x, y, w, h, radiusList } = calcViewBoxSize(viewElem, { diff --git a/packages/renderer/src/draw/text.ts b/packages/renderer/src/draw/text.ts index ebf9f4163..1d549b763 100644 --- a/packages/renderer/src/draw/text.ts +++ b/packages/renderer/src/draw/text.ts @@ -7,7 +7,7 @@ const detailConfig = getDefaultElementDetailConfig(); export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: RendererDrawElementOptions) { const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts; - const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }) || elem; + const { x, y, w, h, angle } = calcViewElementSize(elem, { viewScaleInfo }) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { drawBox(ctx, viewElem, { @@ -21,8 +21,11 @@ export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: Render ...detailConfig, ...elem.detail }; - const fontSize = (detail.fontSize || detailConfig.fontSize) * viewScaleInfo.scale; - const lineHeight = detail.lineHeight ? detail.lineHeight * viewScaleInfo.scale : fontSize; + const originFontSize = detail.fontSize || detailConfig.fontSize; + const fontSize = originFontSize * viewScaleInfo.scale; + + const originLineHeight = detail.lineHeight || originFontSize; + const lineHeight = originLineHeight * viewScaleInfo.scale; ctx.fillStyle = elem.detail.color || detailConfig.color; ctx.textBaseline = 'top'; @@ -42,7 +45,7 @@ export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: Render if (tempText.length > 0) { for (let i = 0; i < tempText.length; i++) { - if (ctx.measureText(lineText + (tempText[i] || '')).width < ctx.$doPixelRatio(w)) { + if (ctx.measureText(lineText + (tempText[i] || '')).width <= ctx.$doPixelRatio(w)) { lineText += tempText[i] || ''; } else { lines.push({ @@ -56,7 +59,7 @@ export function drawText(ctx: ViewContext2D, elem: Element<'text'>, opts: Render break; } if (tempText.length - 1 === i) { - if ((lineNum + 1) * fontHeight < h) { + if ((lineNum + 1) * fontHeight <= h) { lines.push({ text: lineText, width: ctx.$undoPixelRatio(ctx.measureText(lineText).width) diff --git a/packages/types/src/lib/config.ts b/packages/types/src/lib/config.ts index c90296990..70a15ac66 100644 --- a/packages/types/src/lib/config.ts +++ b/packages/types/src/lib/config.ts @@ -1,5 +1,5 @@ import type { ElementBaseDetail, ElementTextDetail, ElementGroupDetail } from './element'; export type DefaultElementDetailConfig = Required> & - Required> & + Required> & Required>; diff --git a/packages/types/src/lib/controller.ts b/packages/types/src/lib/controller.ts index fdb5fd245..9614422bb 100644 --- a/packages/types/src/lib/controller.ts +++ b/packages/types/src/lib/controller.ts @@ -38,3 +38,5 @@ export interface ElementSizeController { rightMiddle: ElementSizeControllerItem; rotate: ElementSizeControllerItem; } + +export type LayoutSizeController = Omit; diff --git a/packages/types/src/lib/data.ts b/packages/types/src/lib/data.ts index 009b956c7..7ea98bb56 100644 --- a/packages/types/src/lib/data.ts +++ b/packages/types/src/lib/data.ts @@ -1,7 +1,20 @@ import type { Element, ElementType, ElementAssets, ElementSize, ElementGroupDetail } from './element'; -export type DataLayout = Pick & { - detail: Omit; +export type DataLayout = Pick & { + detail: Pick< + ElementGroupDetail, + 'background' | 'borderWidth' | 'overflow' | 'borderColor' | 'borderDash' | 'borderRadius' | 'shadowBlur' | 'shadowColor' | 'shadowOffsetX' | 'shadowOffsetY' + >; + operations?: { + disableLeft?: boolean; + disableTop?: boolean; + disableRight?: boolean; + disableBottom?: boolean; + disableTopLeft?: boolean; + disableTopRight?: boolean; + disableBottomLeft?: boolean; + disableBottomRight?: boolean; + }; }; export interface Data = Record> { elements: Element[]; diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 889288a11..f29841a01 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -59,12 +59,14 @@ export { calcElementViewRectInfo, calcElementOriginRectInfo, calcElementViewRectInfoMap, - originRectInfoToRangeRectInfo + originRectInfoToRangeRectInfo, + isViewPointInElementSize, + isViewPointInVertexes } from './lib/view-calc'; 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'; +export { calcElementSizeController, calcLayoutSizeController } from './lib/controller'; export { generateSVGPath, parseSVGPath } from './lib/svg-path'; export { generateHTML, parseHTML } from './lib/html'; export { compressImage } from './lib/image'; diff --git a/packages/util/src/lib/config.ts b/packages/util/src/lib/config.ts index 34ef63ccd..cf2b3865f 100644 --- a/packages/util/src/lib/config.ts +++ b/packages/util/src/lib/config.ts @@ -28,7 +28,7 @@ export function getDefaultElementDetailConfig(): DefaultElementDetailConfig { textAlign: 'left', verticalAlign: 'top', fontSize: 16, - lineHeight: 20, + // lineHeight: 20, fontFamily: 'sans-serif', fontWeight: 400, overflow: 'hidden' diff --git a/packages/util/src/lib/controller.ts b/packages/util/src/lib/controller.ts index 9a1c07b2c..523d7b193 100644 --- a/packages/util/src/lib/controller.ts +++ b/packages/util/src/lib/controller.ts @@ -1,7 +1,9 @@ +import type { Element, ElementSize, ElementSizeController, ViewRectVertexes, PointSize, ViewScaleInfo, LayoutSizeController } from '@idraw/types'; import { createUUID } from './uuid'; import { getCenterFromTwoPoints } from './point'; import { calcElementVertexesInGroup, calcElementVertexes } from './vertex'; -import type { Element, ElementSize, ElementSizeController, ViewRectVertexes, PointSize, ViewScaleInfo } from '@idraw/types'; +import { calcViewElementSize } from './view-calc'; +import { calcElementCenter } from './rotate'; function createControllerElementSizeFromCenter(center: PointSize, opts: { size: number; angle: number }) { const { x, y } = center; @@ -185,3 +187,117 @@ export function calcElementSizeController( }; return sizeController; } + +export function calcLayoutSizeController( + layoutSize: Pick, + opts: { + controllerSize?: number; + viewScaleInfo: ViewScaleInfo; + } +): LayoutSizeController { + const { controllerSize, viewScaleInfo } = opts; + + const ctrlSize = controllerSize && controllerSize > 0 ? controllerSize : 8; + + const { x, y, w, h } = calcViewElementSize(layoutSize, { viewScaleInfo }); + const center = calcElementCenter({ x, y, w, h }); + + const topCenter = { x: center.x, y }; + const rightCenter = { x: x + w, y: center.y }; + const bottomCenter = { x: center.x, y: y + h }; + const leftCenter = { x, y: center.y }; + + const topLeftCenter = { x, y }; + const topRightCenter = { x: x + w, y }; + const bottomRightCenter = { x: x + w, y: y + h }; + const bottomLeftCenter = { x, y: y + h }; + + const topMiddleSize = createControllerElementSizeFromCenter(topCenter, { size: ctrlSize, angle: 0 }); + const rightMiddleSize = createControllerElementSizeFromCenter(rightCenter, { size: ctrlSize, angle: 0 }); + const bottomMiddleSize = createControllerElementSizeFromCenter(bottomCenter, { size: ctrlSize, angle: 0 }); + const leftMiddleSize = createControllerElementSizeFromCenter(leftCenter, { size: ctrlSize, angle: 0 }); + + const topLeftSize = createControllerElementSizeFromCenter(topLeftCenter, { size: ctrlSize, angle: 0 }); + const topRightSize = createControllerElementSizeFromCenter(topRightCenter, { size: ctrlSize, angle: 0 }); + const bottomLeftSize = createControllerElementSizeFromCenter(bottomLeftCenter, { size: ctrlSize, angle: 0 }); + const bottomRightSize = createControllerElementSizeFromCenter(bottomRightCenter, { size: ctrlSize, angle: 0 }); + + const topLeftVertexes = calcElementVertexes(topLeftSize); + const topRightVertexes = calcElementVertexes(topRightSize); + const bottomLeftVertexes = calcElementVertexes(bottomLeftSize); + const bottomRightVertexes = calcElementVertexes(bottomRightSize); + + const topVertexes: ViewRectVertexes = [topLeftVertexes[1], topRightVertexes[0], topRightVertexes[3], topLeftVertexes[2]]; + const rightVertexes: ViewRectVertexes = [topRightVertexes[3], topRightVertexes[2], bottomRightVertexes[1], bottomRightVertexes[0]]; + const bottomVertexes: ViewRectVertexes = [bottomLeftVertexes[1], bottomRightVertexes[0], bottomRightVertexes[3], bottomLeftVertexes[2]]; + const leftVertexes: ViewRectVertexes = [topLeftVertexes[3], topLeftVertexes[2], bottomLeftVertexes[1], bottomLeftVertexes[0]]; + + const topMiddleVertexes = calcElementVertexes(topMiddleSize); + const rightMiddleVertexes = calcElementVertexes(rightMiddleSize); + const bottomMiddleVertexes = calcElementVertexes(bottomMiddleSize); + const leftMiddleVertexes = calcElementVertexes(leftMiddleSize); + + const sizeController: LayoutSizeController = { + left: { + type: 'left', + vertexes: leftVertexes, + center: leftCenter + }, + right: { + type: 'right', + vertexes: rightVertexes, + center: rightCenter + }, + top: { + type: 'top', + vertexes: topVertexes, + center: topCenter + }, + bottom: { + type: 'bottom', + vertexes: bottomVertexes, + center: bottomCenter + }, + topLeft: { + type: 'top-left', + vertexes: topLeftVertexes, + center: topLeftCenter + }, + topRight: { + type: 'top-right', + vertexes: topRightVertexes, + center: topRightCenter + }, + bottomLeft: { + type: 'bottom-left', + vertexes: bottomLeftVertexes, + center: bottomLeftCenter + }, + bottomRight: { + type: 'bottom-right', + vertexes: bottomRightVertexes, + center: bottomRightCenter + }, + leftMiddle: { + type: 'left-middle', + vertexes: leftMiddleVertexes, + center: leftCenter + }, + rightMiddle: { + type: 'right-middle', + vertexes: rightMiddleVertexes, + center: rightCenter + }, + topMiddle: { + type: 'top-middle', + vertexes: topMiddleVertexes, + center: topCenter + }, + bottomMiddle: { + type: 'bottom-middle', + vertexes: bottomMiddleVertexes, + center: bottomCenter + } + }; + return sizeController; +} diff --git a/packages/util/src/lib/view-calc.ts b/packages/util/src/lib/view-calc.ts index fd10909f6..db825040b 100644 --- a/packages/util/src/lib/view-calc.ts +++ b/packages/util/src/lib/view-calc.ts @@ -14,7 +14,7 @@ import { } from '@idraw/types'; import { rotateElementVertexes } from './rotate'; import { checkRectIntersect } from './rect'; -import { calcElementVertexesInGroup } from './vertex'; +import { calcElementVertexesInGroup, calcElementVertexes } from './vertex'; import { getCenterFromTwoPoints } from './point'; export function calcViewScaleInfo(info: { scale: number; offsetX: number; offsetY: number }, opts: { viewSizeInfo: ViewSizeInfo }): ViewScaleInfo { @@ -83,7 +83,7 @@ export function viewScroll(opts: { moveX?: number; moveY?: number; viewScaleInfo }; } -export function calcViewElementSize(size: ElementSize, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): ElementSize { +export function calcViewElementSize(size: ElementSize, opts: { viewScaleInfo: ViewScaleInfo }): ElementSize { const { viewScaleInfo } = opts; const { x, y, w, h, angle } = size; const { scale, offsetTop, offsetLeft } = viewScaleInfo; @@ -126,10 +126,10 @@ export function isViewPointInElement( p: Point, opts: { context2d: ViewContext2D; element: ElementSize; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } ): boolean { - const { context2d: ctx, element: elem, viewScaleInfo, viewSizeInfo } = opts; + const { context2d: ctx, element: elem, viewScaleInfo } = opts; const { angle = 0 } = elem; - const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }); + const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo }); const vertexes = rotateElementVertexes({ x, y, w, h, angle }); if (vertexes.length >= 2) { ctx.beginPath(); @@ -145,6 +145,40 @@ export function isViewPointInElement( return false; } +export function isViewPointInElementSize( + p: Point, + elemSize: ElementSize, + opts?: { + includeBorder?: boolean; + } +): boolean { + const vertexes = calcElementVertexes(elemSize); + return isViewPointInVertexes(p, vertexes, opts); +} + +export function isViewPointInVertexes( + p: Point, + vertexes: ViewRectVertexes, + opts?: { + includeBorder?: boolean; + } +): boolean { + const xList = [vertexes[0].x, vertexes[1].x, vertexes[2].x, vertexes[3].x]; + const yList = [vertexes[0].y, vertexes[1].y, vertexes[2].y, vertexes[3].y]; + const mixX = Math.min(...xList); + const maxX = Math.max(...xList); + const mixY = Math.min(...yList); + const maxY = Math.max(...yList); + + if (p.x > mixX && p.x < maxX && p.y > mixY && p.y < maxY) { + return true; + } + if (opts?.includeBorder === true && (p.x === mixX || p.x === maxX || p.y === mixY || p.y === maxY)) { + return true; + } + return false; +} + export function getViewPointAtElement( p: Point, opts: { @@ -234,7 +268,7 @@ export function isElementInView(elem: ElementSize, opts: { viewScaleInfo: ViewSc const { viewSizeInfo, viewScaleInfo } = opts; const { width, height } = viewSizeInfo; const { angle } = elem; - const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo, viewSizeInfo }); + const { x, y, w, h } = calcViewElementSize(elem, { viewScaleInfo }); const ves = rotateElementVertexes({ x, y, w, h, angle }); const viewSize = { x: 0, y: 0, w: width, h: height }; diff --git a/packages/util/src/lib/view-content.ts b/packages/util/src/lib/view-content.ts index 3f764b62b..9e6673b20 100644 --- a/packages/util/src/lib/view-content.ts +++ b/packages/util/src/lib/view-content.ts @@ -2,6 +2,7 @@ import type { Data, ViewSizeInfo, Element, ElementSize, ViewScaleInfo, PointSize import { rotateElementVertexes } from './rotate'; import {} from './view-calc'; import { formatNumber } from './number'; +import { is } from './is'; interface ViewCenterContentResult { offsetX: number; @@ -58,6 +59,16 @@ export function calcViewCenterContent(data: Data, opts: { viewSizeInfo: ViewSize }); } + if (data.layout) { + const { x, y, w, h } = data.layout; + if (is.x(x) && is.y(y) && is.w(w) && is.h(h)) { + contentX = Math.min(contentX, x); + contentY = Math.min(contentY, y); + contentW = Math.max(contentW, w); + contentH = Math.max(contentH, h); + } + } + if (contentW > 0 && contentH > 0) { const scaleW = formatNumber(width / contentW, { decimalPlaces: 4 }); const scaleH = formatNumber(height / contentH, { decimalPlaces: 4 });