diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index aa882ea97..bc06c332a 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -278,6 +278,10 @@ export class Board { return this._sharer; } + getViewer() { + return this._viewer; + } + setData(data: Data): { viewSizeInfo: ViewSizeInfo } { const sharer = this._sharer; this._sharer.setActiveStorage('data', data); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c9fafb0b4..3a02606fa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -56,6 +56,8 @@ export class Core { scale(opts: { scale: number; point: PointSize }) { this._board.scale(opts); + const viewer = this._board.getViewer(); + viewer.drawFrame(); } resize(newViewSize: Partial) { diff --git a/packages/core/src/middleware/scaler/index.ts b/packages/core/src/middleware/scaler/index.ts index fa3428740..1d88a7ca3 100644 --- a/packages/core/src/middleware/scaler/index.ts +++ b/packages/core/src/middleware/scaler/index.ts @@ -1,8 +1,10 @@ -import type { PointSize, BoardMiddleware, ViewScaleInfo, ViewSizeInfo } from '@idraw/types'; +import type { BoardMiddleware, CoreEvent } from '@idraw/types'; +import { formatNumber } from '@idraw/util'; -export const MiddlewareScaler: BoardMiddleware = (opts) => { +export const MiddlewareScaler: BoardMiddleware, CoreEvent> = (opts) => { const key = 'SCALE'; - const { viewer, sharer } = opts; + const { viewer, sharer, eventHub } = opts; + return { mode: key, isDefault: true, @@ -18,6 +20,8 @@ export const MiddlewareScaler: BoardMiddleware = (opts) => { const { moveX, moveY } = viewer.scale({ scale: newScaleNum, point }); viewer.scroll({ moveX, moveY }); viewer.drawFrame(); + const scaleNum = formatNumber(scale); + eventHub.trigger('scale', { scale: scaleNum }); } }; }; diff --git a/packages/core/src/middleware/selector/draw-wrapper.ts b/packages/core/src/middleware/selector/draw-wrapper.ts index d959c1f52..8e0736773 100644 --- a/packages/core/src/middleware/selector/draw-wrapper.ts +++ b/packages/core/src/middleware/selector/draw-wrapper.ts @@ -65,10 +65,10 @@ export function drawSelectedElementControllersVertexes( const ctrlOpts = { ...wrapperOpts, borderWidth: resizeControllerBorderWidth, background: '#FFFFFF' }; drawVertexes(ctx, calcViewVertexes(elementWrapper, opts), wrapperOpts); - drawVertexes(ctx, calcViewVertexes(left.vertexes, opts), ctrlOpts); - drawVertexes(ctx, calcViewVertexes(right.vertexes, opts), ctrlOpts); - drawVertexes(ctx, calcViewVertexes(top.vertexes, opts), ctrlOpts); - drawVertexes(ctx, calcViewVertexes(bottom.vertexes, opts), ctrlOpts); + // drawVertexes(ctx, calcViewVertexes(left.vertexes, opts), ctrlOpts); + // drawVertexes(ctx, calcViewVertexes(right.vertexes, opts), ctrlOpts); + // drawVertexes(ctx, calcViewVertexes(top.vertexes, opts), ctrlOpts); + // drawVertexes(ctx, calcViewVertexes(bottom.vertexes, opts), ctrlOpts); drawVertexes(ctx, calcViewVertexes(topLeft.vertexes, opts), ctrlOpts); drawVertexes(ctx, calcViewVertexes(topRight.vertexes, opts), ctrlOpts); drawVertexes(ctx, calcViewVertexes(bottomLeft.vertexes, opts), ctrlOpts); diff --git a/packages/renderer/src/draw/base.ts b/packages/renderer/src/draw/box.ts similarity index 90% rename from packages/renderer/src/draw/base.ts rename to packages/renderer/src/draw/box.ts index 06f225db8..b442622fa 100644 --- a/packages/renderer/src/draw/base.ts +++ b/packages/renderer/src/draw/box.ts @@ -1,5 +1,5 @@ import { ViewContext2D, Element, ElementType, ElementSize, ViewScaleInfo, ViewSizeInfo, TransformAction } from '@idraw/types'; -import { istype, isColorStr, generateSVGPath, rotateElement, is, getDefaultElementDetailConfig } from '@idraw/util'; +import { istype, isColorStr, generateSVGPath, rotateElement, is, getDefaultElementDetailConfig, calcViewBoxSize } from '@idraw/util'; import { createColorStyle } from './color'; const defaultElemConfig = getDefaultElementDetailConfig(); @@ -87,43 +87,18 @@ function drawBoxBackground( viewElem: Element, opts: { pattern?: string | CanvasPattern | null; viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } ): void { - const { pattern, viewScaleInfo } = opts; - const { scale } = viewScaleInfo; + const { pattern, viewScaleInfo, viewSizeInfo } = opts; let transform: TransformAction[] = []; - let { borderRadius, boxSizing = defaultElemConfig.boxSizing, borderWidth } = viewElem.detail; + let { borderRadius, borderWidth } = viewElem.detail; if (typeof borderWidth !== 'number') { // TODO: If borderWidth is an array, borderRadius will not take effect and will become 0. borderRadius = 0; } if (viewElem.detail.background || pattern) { - let { x, y, w, h } = viewElem; - let radiusList: [number, number, number, number] = [0, 0, 0, 0]; - if (typeof borderRadius === 'number') { - const br = borderRadius * scale; - radiusList = [br, br, br, br]; - } else if (Array.isArray(borderRadius) && borderRadius?.length === 4) { - radiusList = [borderRadius[0] * scale, borderRadius[1] * scale, borderRadius[2] * scale, borderRadius[3] * scale]; - } - let bw: number = 0; - if (typeof borderWidth === 'number') { - bw = (borderWidth || 1) * scale; - } - if (boxSizing === 'border-box') { - x = viewElem.x + bw / 2; - y = viewElem.y + bw / 2; - w = viewElem.w - bw; - h = viewElem.h - bw; - } else if (boxSizing === 'content-box') { - x = viewElem.x - bw / 2; - y = viewElem.y - bw / 2; - w = viewElem.w + bw; - h = viewElem.h + bw; - } else { - x = viewElem.x; - y = viewElem.y; - w = viewElem.w; - h = viewElem.h; - } + const { x, y, w, h, radiusList } = calcViewBoxSize(viewElem, { + viewScaleInfo, + viewSizeInfo + }); // r = Math.min(r, w / 2, h / 2); // if (w < r * 2 || h < r * 2) { diff --git a/packages/renderer/src/draw/circle.ts b/packages/renderer/src/draw/circle.ts index 9ad40c1de..bd4e89a75 100644 --- a/packages/renderer/src/draw/circle.ts +++ b/packages/renderer/src/draw/circle.ts @@ -1,7 +1,7 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types'; import { rotateElement } from '@idraw/util'; import { createColorStyle } from './color'; -import { drawBoxShadow } from './base'; +import { drawBoxShadow } from './box'; export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: RendererDrawElementOptions) { const { detail, angle } = elem; diff --git a/packages/renderer/src/draw/group.ts b/packages/renderer/src/draw/group.ts index 30d96b605..a95dbd0db 100644 --- a/packages/renderer/src/draw/group.ts +++ b/packages/renderer/src/draw/group.ts @@ -6,7 +6,7 @@ import { drawImage } from './image'; import { drawText } from './text'; import { drawSVG } from './svg'; import { drawHTML } from './html'; -import { drawBox } from './base'; +import { drawBox } from './box'; import { drawPath } from './path'; export function drawElement(ctx: ViewContext2D, elem: Element, opts: RendererDrawElementOptions) { diff --git a/packages/renderer/src/draw/html.ts b/packages/renderer/src/draw/html.ts index dd9bb143c..7df20402c 100644 --- a/packages/renderer/src/draw/html.ts +++ b/packages/renderer/src/draw/html.ts @@ -2,15 +2,18 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/ import { rotateElement } from '@idraw/util'; export function drawHTML(ctx: ViewContext2D, elem: Element<'html'>, opts: RendererDrawElementOptions) { - const content = opts.loader.getContent(elem.uuid); + const content = opts.loader.getContent(elem); const { calculator, viewScaleInfo, viewSizeInfo } = opts; const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); rotateElement(ctx, { x, y, w, h, angle }, () => { if (!content) { - opts.loader.load(elem as Element<'html'>); + opts.loader.load(elem as Element<'html'>, opts.elementAssets || {}); } if (elem.type === 'html' && content) { + const { opacity } = elem.detail; + ctx.globalAlpha = opacity ? opacity : 1; ctx.drawImage(content, x, y, w, h); + ctx.globalAlpha = 1; } }); } diff --git a/packages/renderer/src/draw/image.ts b/packages/renderer/src/draw/image.ts index 3c7b44d68..948447c04 100644 --- a/packages/renderer/src/draw/image.ts +++ b/packages/renderer/src/draw/image.ts @@ -1,16 +1,54 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types'; -import { rotateElement } from '@idraw/util'; +import { rotateElement, calcViewBoxSize } from '@idraw/util'; +import { drawBox, drawBoxShadow } from './box'; export function drawImage(ctx: ViewContext2D, elem: Element<'image'>, opts: RendererDrawElementOptions) { const content = opts.loader.getContent(elem); const { calculator, viewScaleInfo, viewSizeInfo } = opts; const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); + + const viewElem = { ...elem, ...{ x, y, w, h, angle } }; rotateElement(ctx, { x, y, w, h, angle }, () => { - if (!content) { - opts.loader.load(elem as Element<'image'>, opts.elementAssets || {}); - } - if (elem.type === 'image' && content) { - ctx.drawImage(content, x, y, w, h); - } + drawBoxShadow(ctx, viewElem, { + viewScaleInfo, + viewSizeInfo, + renderContent: () => { + drawBox(ctx, viewElem, { + originElem: elem, + calcElemSize: { x, y, w, h, angle }, + viewScaleInfo, + viewSizeInfo, + renderContent: () => { + if (!content) { + opts.loader.load(elem as Element<'image'>, opts.elementAssets || {}); + } + if (elem.type === 'image' && content) { + const { opacity } = elem.detail; + ctx.globalAlpha = opacity ? opacity : 1; + const { x, y, w, h, radiusList } = calcViewBoxSize(viewElem, { + viewScaleInfo, + viewSizeInfo + }); + + ctx.save(); + + 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(); + ctx.drawImage(content, x, y, w, h); + ctx.globalAlpha = 1; + + ctx.restore(); + } + } + }); + } + }); }); } diff --git a/packages/renderer/src/draw/path.ts b/packages/renderer/src/draw/path.ts index f4ccce2f6..2f47f7865 100644 --- a/packages/renderer/src/draw/path.ts +++ b/packages/renderer/src/draw/path.ts @@ -1,6 +1,6 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types'; import { rotateElement, generateSVGPath } from '@idraw/util'; -import { drawBox, drawBoxShadow } from './base'; +import { drawBox, drawBoxShadow } from './box'; export function drawPath(ctx: ViewContext2D, elem: Element<'path'>, opts: RendererDrawElementOptions) { const { detail } = elem; diff --git a/packages/renderer/src/draw/rect.ts b/packages/renderer/src/draw/rect.ts index a8e40aa6a..11600ad66 100644 --- a/packages/renderer/src/draw/rect.ts +++ b/packages/renderer/src/draw/rect.ts @@ -1,6 +1,6 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types'; import { rotateElement } from '@idraw/util'; -import { drawBox, drawBoxShadow } from './base'; +import { drawBox, drawBoxShadow } from './box'; export function drawRect(ctx: ViewContext2D, elem: Element<'rect'>, opts: RendererDrawElementOptions) { const { calculator, viewScaleInfo, viewSizeInfo } = opts; diff --git a/packages/renderer/src/draw/svg.ts b/packages/renderer/src/draw/svg.ts index 7e12c7578..477315915 100644 --- a/packages/renderer/src/draw/svg.ts +++ b/packages/renderer/src/draw/svg.ts @@ -2,7 +2,7 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/ import { rotateElement } from '@idraw/util'; export function drawSVG(ctx: ViewContext2D, elem: Element<'svg'>, opts: RendererDrawElementOptions) { - const content = opts.loader.getContent(elem.uuid); + const content = opts.loader.getContent(elem); const { calculator, viewScaleInfo, viewSizeInfo } = opts; const { x, y, w, h, angle } = calculator.elementSize(elem, viewScaleInfo, viewSizeInfo); rotateElement(ctx, { x, y, w, h, angle }, () => { @@ -10,7 +10,10 @@ export function drawSVG(ctx: ViewContext2D, elem: Element<'svg'>, opts: Renderer opts.loader.load(elem as Element<'svg'>, opts.elementAssets || {}); } if (elem.type === 'svg' && content) { + const { opacity } = elem.detail; + ctx.globalAlpha = opacity ? opacity : 1; ctx.drawImage(content, x, y, w, h); + ctx.globalAlpha = 1; } }); } diff --git a/packages/renderer/src/draw/text.ts b/packages/renderer/src/draw/text.ts index 0966052ec..adc80d3b4 100644 --- a/packages/renderer/src/draw/text.ts +++ b/packages/renderer/src/draw/text.ts @@ -1,7 +1,7 @@ import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types'; import { rotateElement } from '@idraw/util'; import { is, isColorStr, getDefaultElementDetailConfig } from '@idraw/util'; -import { drawBox } from './base'; +import { drawBox } from './box'; const detailConfig = getDefaultElementDetailConfig(); diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts index c84238cdf..d4f7a212c 100644 --- a/packages/types/src/lib/core.ts +++ b/packages/types/src/lib/core.ts @@ -29,9 +29,13 @@ export interface CoreEventSelect { export interface CoreEventChange { data: Data; } +export interface CoreEventScale { + scale: number; +} export type CoreEvent = { cursor: CoreEventCursor; select: CoreEventSelect; change: CoreEventChange; + scale: CoreEventScale; }; diff --git a/packages/types/src/lib/view.ts b/packages/types/src/lib/view.ts index 3817ce5d3..9e59e6249 100644 --- a/packages/types/src/lib/view.ts +++ b/packages/types/src/lib/view.ts @@ -43,3 +43,11 @@ export interface ViewCalculator { } export type ViewRectVertexes = [PointSize, PointSize, PointSize, PointSize]; + +export interface ViewBoxSize { + x: number; + y: number; + w: number; + h: number; + radiusList: [number, number, number, number]; +} diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 7938e72d6..4b219e607 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -1,5 +1,5 @@ export { delay, compose, throttle } from './lib/time'; -export { downloadImageFromCanvas, parseFileToBase64, pickFile } from './lib/file'; +export { downloadImageFromCanvas, parseFileToBase64, pickFile, parseFileToText } from './lib/file'; export { toColorHexStr, toColorHexNum, isColorStr, colorNameToHex, colorToCSS, colorToLinearGradientCSS, mergeHexColorAlpha } from './lib/color'; export { createUUID, isAssetId, createAssetId } from './lib/uuid'; export { deepClone, sortDataAsserts } from './lib/data'; @@ -59,3 +59,4 @@ export { compressImage } from './lib/image'; export { formatNumber } from './lib/number'; export { matrixToAngle, matrixToRadian } from './lib/matrix'; export { getDefaultElementDetailConfig } from './lib/config'; +export { calcViewBoxSize } from './lib/view-box'; diff --git a/packages/util/src/lib/controller.ts b/packages/util/src/lib/controller.ts index 34b08308f..617612ca8 100644 --- a/packages/util/src/lib/controller.ts +++ b/packages/util/src/lib/controller.ts @@ -58,55 +58,70 @@ export function calcElementSizeController( const bottomRightCenter = vertexes[2]; const bottomLeftCenter = vertexes[3]; - const topSize = createControllerElementSizeFromCenter(topCenter, { size: ctrlSize, angle: totalAngle }); - const rightSize = createControllerElementSizeFromCenter(rightCenter, { size: ctrlSize, angle: totalAngle }); - const bottomSize = createControllerElementSizeFromCenter(bottomCenter, { size: ctrlSize, angle: totalAngle }); - const leftSize = createControllerElementSizeFromCenter(leftCenter, { size: ctrlSize, angle: totalAngle }); + // const topSize = createControllerElementSizeFromCenter(topCenter, { size: ctrlSize, angle: totalAngle }); + // const rightSize = createControllerElementSizeFromCenter(rightCenter, { size: ctrlSize, angle: totalAngle }); + // const bottomSize = createControllerElementSizeFromCenter(bottomCenter, { size: ctrlSize, angle: totalAngle }); + // const leftSize = createControllerElementSizeFromCenter(leftCenter, { size: ctrlSize, angle: totalAngle }); + const topLeftSize = createControllerElementSizeFromCenter(topLeftCenter, { size: ctrlSize, angle: totalAngle }); const topRightSize = createControllerElementSizeFromCenter(topRightCenter, { size: ctrlSize, angle: totalAngle }); const bottomLeftSize = createControllerElementSizeFromCenter(bottomLeftCenter, { size: ctrlSize, angle: totalAngle }); const bottomRightSize = createControllerElementSizeFromCenter(bottomRightCenter, { size: ctrlSize, angle: totalAngle }); + 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 topVertexes = calcElementVertexes(topSize); + // const rightVertexes = calcElementVertexes(rightSize); + // const bottomVertexes = calcElementVertexes(bottomSize); + // const leftVertexes = calcElementVertexes(leftSize); + const sizeController: ElementSizeController = { elementWrapper: vertexes, left: { type: 'left', - vertexes: calcElementVertexes(leftSize), + vertexes: leftVertexes, center: leftCenter }, right: { type: 'right', - vertexes: calcElementVertexes(rightSize), + vertexes: rightVertexes, center: rightCenter }, top: { type: 'top', - vertexes: calcElementVertexes(topSize), + vertexes: topVertexes, center: topCenter }, bottom: { type: 'bottom', - vertexes: calcElementVertexes(bottomSize), + vertexes: bottomVertexes, center: bottomCenter }, topLeft: { type: 'top-left', - vertexes: calcElementVertexes(topLeftSize), + vertexes: topLeftVertexes, center: topLeftCenter }, topRight: { type: 'top-right', - vertexes: calcElementVertexes(topRightSize), + vertexes: topRightVertexes, center: topRightCenter }, bottomLeft: { type: 'bottom-left', - vertexes: calcElementVertexes(bottomLeftSize), + vertexes: bottomLeftVertexes, center: bottomLeftCenter }, bottomRight: { type: 'bottom-right', - vertexes: calcElementVertexes(bottomRightSize), + vertexes: bottomRightVertexes, center: bottomRightCenter } }; diff --git a/packages/util/src/lib/view-box.ts b/packages/util/src/lib/view-box.ts new file mode 100644 index 000000000..f729ac5ff --- /dev/null +++ b/packages/util/src/lib/view-box.ts @@ -0,0 +1,62 @@ +import type { Element, ViewScaleInfo, ViewSizeInfo, ViewBoxSize } from '@idraw/types'; +import { getDefaultElementDetailConfig } from './config'; +const defaultElemConfig = getDefaultElementDetailConfig(); + +export function calcViewBoxSize(viewElem: Element, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): ViewBoxSize { + const { viewScaleInfo } = opts; + const { scale } = viewScaleInfo; + let { borderRadius, boxSizing = defaultElemConfig.boxSizing, borderWidth } = viewElem.detail; + if (typeof borderWidth !== 'number') { + // TODO: If borderWidth is an array, borderRadius will not take effect and will become 0. + borderRadius = 0; + } + let { x, y, w, h } = viewElem; + let radiusList: [number, number, number, number] = [0, 0, 0, 0]; + if (typeof borderRadius === 'number') { + const br = borderRadius * scale; + radiusList = [br, br, br, br]; + } else if (Array.isArray(borderRadius) && borderRadius?.length === 4) { + radiusList = [borderRadius[0] * scale, borderRadius[1] * scale, borderRadius[2] * scale, borderRadius[3] * scale]; + } + let bw: number = 0; + if (typeof borderWidth === 'number') { + bw = (borderWidth || 1) * scale; + } + if (boxSizing === 'border-box') { + x = viewElem.x + bw / 2; + y = viewElem.y + bw / 2; + w = viewElem.w - bw; + h = viewElem.h - bw; + } else if (boxSizing === 'content-box') { + x = viewElem.x - bw / 2; + y = viewElem.y - bw / 2; + w = viewElem.w + bw; + h = viewElem.h + bw; + } else { + x = viewElem.x; + y = viewElem.y; + w = viewElem.w; + h = viewElem.h; + } + + // r = Math.min(r, w / 2, h / 2); + // if (w < r * 2 || h < r * 2) { + // r = 0; + // } + + // 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(); + + return { + x, + y, + w, + h, + radiusList + }; +}