From ef7e53ee4c359037ed4c50d316735acc91213b84 Mon Sep 17 00:00:00 2001 From: chenshenhai Date: Sat, 2 Mar 2024 12:00:53 +0800 Subject: [PATCH] feat: optimize render and events --- packages/board/src/index.ts | 8 +- packages/core/src/index.ts | 6 +- packages/core/src/lib/cursor.ts | 8 +- packages/core/src/middleware/dragger/index.ts | 4 +- packages/core/src/middleware/ruler/index.ts | 4 +- packages/core/src/middleware/scaler/index.ts | 4 +- .../core/src/middleware/selector/config.ts | 1 + .../src/middleware/selector/draw-auxiliary.ts | 69 +++++++++ .../core/src/middleware/selector/draw-base.ts | 109 +++++++++++++ .../src/middleware/selector/draw-wrapper.ts | 146 ++++-------------- .../core/src/middleware/selector/index.ts | 28 +++- .../core/src/middleware/selector/types.ts | 2 + .../core/src/middleware/text-editor/index.ts | 76 ++++++++- packages/idraw/src/event.ts | 57 ++++--- packages/idraw/src/idraw.ts | 28 ++-- packages/idraw/src/index.ts | 1 + packages/renderer/src/draw/box.ts | 6 + packages/renderer/src/draw/circle.ts | 12 +- packages/renderer/src/index.ts | 4 +- packages/renderer/src/loader.ts | 3 + packages/types/src/lib/board.ts | 12 +- packages/types/src/lib/context2d.ts | 1 + packages/types/src/lib/core.ts | 3 +- packages/types/src/lib/middleware.ts | 4 +- packages/types/src/lib/store.ts | 7 +- packages/util/src/lib/context2d.ts | 13 ++ 26 files changed, 420 insertions(+), 196 deletions(-) create mode 100644 packages/core/src/middleware/selector/draw-auxiliary.ts create mode 100644 packages/core/src/middleware/selector/draw-base.ts diff --git a/packages/board/src/index.ts b/packages/board/src/index.ts index 1dc2c71..1ddba58 100644 --- a/packages/board/src/index.ts +++ b/packages/board/src/index.ts @@ -8,7 +8,7 @@ import type { BoardWatcherEventMap, ViewSizeInfo, PointSize, - BoardExtendEvent, + BoardExtendEventMap, UtilEventEmitter } from '@idraw/types'; import { Calculator } from './lib/calculator'; @@ -23,7 +23,7 @@ interface BoardMiddlewareMapItem { middlewareObject: BoardMiddlewareObject; } -export class Board { +export class Board { #opts: BoardOptions; #middlewareMap: WeakMap = new WeakMap(); #middlewares: BoardMiddleware[] = []; @@ -120,6 +120,10 @@ export class Board { this.#watcher.on('scrollY', this.#handleScrollY.bind(this)); this.#watcher.on('resize', this.#handleResize.bind(this)); this.#watcher.on('doubleClick', this.#handleDoubleClick.bind(this)); + + this.#renderer.on('load', () => { + this.#eventHub.trigger('loadResource'); + }); } #handlePointStart(e: BoardWatcherEventMap['pointStart']) { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 63a0a6d..7b7645b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,4 @@ -import type { Data, PointSize, CoreOptions, BoardMiddleware, ViewSizeInfo, CoreEvent, ViewScaleInfo, LoadItemMap } from '@idraw/types'; +import type { Data, PointSize, CoreOptions, BoardMiddleware, ViewSizeInfo, CoreEventMap, ViewScaleInfo, LoadItemMap } from '@idraw/types'; import { Board } from '@idraw/board'; import { createBoardContent, validateElements } from '@idraw/util'; import { Cursor } from './lib/cursor'; @@ -8,10 +8,10 @@ export { MiddlewareSelector, middlewareEventSelect, middlewareEventSelectClear } export { MiddlewareScroller } from './middleware/scroller'; export { MiddlewareScaler, middlewareEventScale } from './middleware/scaler'; export { MiddlewareRuler, middlewareEventRuler } from './middleware/ruler'; -export { MiddlewareTextEditor, middlewareEventTextEdit } from './middleware/text-editor'; +export { MiddlewareTextEditor, middlewareEventTextEdit, middlewareEventTextChange } from './middleware/text-editor'; export { MiddlewareDragger } from './middleware/dragger'; -export class Core { +export class Core { #board: Board; // #opts: CoreOptions; #canvas: HTMLCanvasElement; diff --git a/packages/core/src/lib/cursor.ts b/packages/core/src/lib/cursor.ts index 62ec6db..a938944 100644 --- a/packages/core/src/lib/cursor.ts +++ b/packages/core/src/lib/cursor.ts @@ -1,9 +1,9 @@ -import type { UtilEventEmitter, CoreEvent } from '@idraw/types'; +import type { UtilEventEmitter, CoreEventMap } from '@idraw/types'; import { limitAngle, loadImage, parseAngleToRadian } from '@idraw/util'; import { CURSOR, CURSOR_RESIZE, CURSOR_DRAG_DEFAULT, CURSOR_DRAG_ACTIVE, CURSOR_RESIZE_ROTATE } from './cursor-image'; export class Cursor { - #eventHub: UtilEventEmitter; + #eventHub: UtilEventEmitter; #container: HTMLDivElement; #cursorType: 'default' | string | null = null; #resizeCursorBaseImage: HTMLImageElement | null = null; @@ -17,7 +17,7 @@ export class Cursor { constructor( container: HTMLDivElement, opts: { - eventHub: UtilEventEmitter; + eventHub: UtilEventEmitter; } ) { this.#container = container; @@ -78,7 +78,7 @@ export class Cursor { } } - #setCursorResize(e: CoreEvent['cursor']) { + #setCursorResize(e: CoreEventMap['cursor']) { let totalAngle = 0; if (e.type === 'resize-top') { totalAngle += 0; diff --git a/packages/core/src/middleware/dragger/index.ts b/packages/core/src/middleware/dragger/index.ts index 233c541..7506e33 100644 --- a/packages/core/src/middleware/dragger/index.ts +++ b/packages/core/src/middleware/dragger/index.ts @@ -1,4 +1,4 @@ -import type { BoardMiddleware, CoreEvent, Point } from '@idraw/types'; +import type { BoardMiddleware, CoreEventMap, Point } from '@idraw/types'; const key = 'DRAG'; const keyPrevPoint = Symbol(`${key}_prevPoint`); @@ -7,7 +7,7 @@ type DraggerSharedStorage = { [keyPrevPoint]: Point | null; }; -export const MiddlewareDragger: BoardMiddleware = (opts) => { +export const MiddlewareDragger: BoardMiddleware = (opts) => { const { eventHub, sharer, viewer } = opts; let isDragging = false; diff --git a/packages/core/src/middleware/ruler/index.ts b/packages/core/src/middleware/ruler/index.ts index 44470d3..7a95e6e 100644 --- a/packages/core/src/middleware/ruler/index.ts +++ b/packages/core/src/middleware/ruler/index.ts @@ -1,10 +1,10 @@ -import type { BoardMiddleware, CoreEvent } from '@idraw/types'; +import type { BoardMiddleware, CoreEventMap } from '@idraw/types'; import { getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot } from '@idraw/util'; import { drawRulerBackground, drawXRuler, drawYRuler, calcXRulerScaleList, calcYRulerScaleList, drawUnderGrid } from './util'; export const middlewareEventRuler = '@middleware/show-ruler'; -export const MiddlewareRuler: BoardMiddleware, CoreEvent> = (opts) => { +export const MiddlewareRuler: BoardMiddleware, CoreEventMap> = (opts) => { const { boardContent, viewer, eventHub } = opts; const { helperContext, underContext } = boardContent; let show: boolean = true; diff --git a/packages/core/src/middleware/scaler/index.ts b/packages/core/src/middleware/scaler/index.ts index 4b60912..85008f7 100644 --- a/packages/core/src/middleware/scaler/index.ts +++ b/packages/core/src/middleware/scaler/index.ts @@ -1,9 +1,9 @@ -import type { BoardMiddleware, CoreEvent } from '@idraw/types'; +import type { BoardMiddleware, CoreEventMap } from '@idraw/types'; import { formatNumber } from '@idraw/util'; export const middlewareEventScale = '@middleware/scale'; -export const MiddlewareScaler: BoardMiddleware, CoreEvent> = (opts) => { +export const MiddlewareScaler: BoardMiddleware, CoreEventMap> = (opts) => { const { viewer, sharer, eventHub } = opts; const maxScale = 50; const minScale = 0.05; diff --git a/packages/core/src/middleware/selector/config.ts b/packages/core/src/middleware/selector/config.ts index 5deceb2..03f23e2 100644 --- a/packages/core/src/middleware/selector/config.ts +++ b/packages/core/src/middleware/selector/config.ts @@ -12,6 +12,7 @@ export const keySelectedElementListVertexes = Symbol(`${key}_selectedElementList export const keySelectedElementController = Symbol(`${key}_selectedElementController`); // ElementSizeController export const keyGroupQueue = Symbol(`${key}_groupQueue`); // Array> | [] export const keyGroupQueueVertexesList = Symbol(`${key}_groupQueueVertexesList`); // Array | [] +export const keyIsMoving = Symbol(`${key}_isMoving`); // boolean | null export const keyDebugElemCenter = Symbol(`${key}_debug_elemCenter`); export const keyDebugStartVertical = Symbol(`${key}_debug_startVertical`); diff --git a/packages/core/src/middleware/selector/draw-auxiliary.ts b/packages/core/src/middleware/selector/draw-auxiliary.ts new file mode 100644 index 0000000..7c8b00e --- /dev/null +++ b/packages/core/src/middleware/selector/draw-auxiliary.ts @@ -0,0 +1,69 @@ +import type { ViewContext2D, ViewRectVertexes, Element } from '@idraw/types'; +import { drawLine } from './draw-base'; + +const auxiliaryColor = '#f7276e'; +// const auxiliaryTextColor = '#FFFFFF'; +// const fontSize = 12; +// const fontFamily = 'Consolas,Monaco,monospace'; +// const fontWeight = 600; + +// function drawLabel( +// ctx, +// opts: { +// text: string; +// start: PointSize; +// textColor: string; +// background: string; +// } +// ) { +// // TODO +// } + +export function drawSizeAuxiliaryLines( + ctx: ViewContext2D, + opts: { + vertexes: ViewRectVertexes; + element: Element | null; + groupQueue: Element<'group'>[]; + } +) { + const { vertexes, element, groupQueue } = opts; + const point = vertexes[0]; + const lineOpts = { + borderColor: auxiliaryColor, + borderWidth: 1, + lineDash: [] + // lineDash: [4, 4] + }; + if (groupQueue.length > 0) { + // TODO + } else { + if (!element?.angle) { + drawLine(ctx, { x: point.x, y: 0 }, point, lineOpts); + drawLine(ctx, { x: 0, y: point.y }, point, lineOpts); + + // { + // const xNum = `${element?.x}`; + // const w = xNum.length * fontSize; + // const h = fontSize; + // const x = point.x / 2 - xNum.length * fontSize; + // const y = point.y + fontSize / 2; + // ctx.$setFont({ + // fontSize, + // fontFamily, + // fontWeight + // }); + // ctx.moveTo(x, y); + // ctx.lineTo(x + w, y); + // ctx.lineTo(x + w, y + h); + // ctx.lineTo(x, y + h); + // ctx.lineTo(x, y); + // ctx.fillStyle = auxiliaryColor; + // ctx.fill(); + + // ctx.fillStyle = auxiliaryTextColor; + // ctx.fillText(xNum, x, y, w); + // } + } + } +} diff --git a/packages/core/src/middleware/selector/draw-base.ts b/packages/core/src/middleware/selector/draw-base.ts new file mode 100644 index 0000000..543adf6 --- /dev/null +++ b/packages/core/src/middleware/selector/draw-base.ts @@ -0,0 +1,109 @@ +import type { PointSize, ViewContext2D, ViewRectVertexes } from '@idraw/types'; + +export function drawVertexes( + ctx: ViewContext2D, + vertexes: ViewRectVertexes, + opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[] } +) { + const { borderColor, borderWidth, background, lineDash } = opts; + ctx.setLineDash([]); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = borderColor; + ctx.fillStyle = background; + ctx.setLineDash(lineDash); + ctx.beginPath(); + ctx.moveTo(vertexes[0].x, vertexes[0].y); + ctx.lineTo(vertexes[1].x, vertexes[1].y); + ctx.lineTo(vertexes[2].x, vertexes[2].y); + ctx.lineTo(vertexes[3].x, vertexes[3].y); + ctx.lineTo(vertexes[0].x, vertexes[0].y); + ctx.closePath(); + ctx.stroke(); + ctx.fill(); +} + +export function drawLine(ctx: ViewContext2D, start: PointSize, end: PointSize, opts: { borderColor: string; borderWidth: number; lineDash: number[] }) { + const { borderColor, borderWidth, lineDash } = opts; + ctx.setLineDash([]); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = borderColor; + ctx.setLineDash(lineDash); + ctx.beginPath(); + ctx.moveTo(start.x, start.y); + ctx.lineTo(end.x, end.y); + ctx.closePath(); + ctx.stroke(); +} + +export function drawCircleController( + ctx: ViewContext2D, + circleCenter: PointSize, + opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[]; size: number } +) { + const { size, borderColor, borderWidth, background } = opts; + const center = circleCenter; + const r = size / 2; + + const a = r; + const b = r; + // 'content-box' + + if (a >= 0 && b >= 0) { + // draw border + if (typeof borderWidth === 'number' && borderWidth > 0) { + const ba = borderWidth / 2 + a; + const bb = borderWidth / 2 + b; + ctx.beginPath(); + ctx.strokeStyle = borderColor; + ctx.lineWidth = borderWidth; + ctx.circle(center.x, center.y, ba, bb, 0, 0, 2 * Math.PI); + ctx.closePath(); + ctx.stroke(); + } + + // draw content + ctx.beginPath(); + ctx.fillStyle = background; + ctx.circle(center.x, center.y, a, b, 0, 0, 2 * Math.PI); + ctx.closePath(); + ctx.fill(); + } + + // ctx.setLineDash([]); + // ctx.lineWidth = borderWidth; + // ctx.strokeStyle = borderColor; + // ctx.fillStyle = background; + // ctx.setLineDash(lineDash); + // ctx.beginPath(); + // ctx.moveTo(vertexes[0].x, vertexes[0].y); + // ctx.lineTo(vertexes[1].x, vertexes[1].y); + // ctx.lineTo(vertexes[2].x, vertexes[2].y); + // ctx.lineTo(vertexes[3].x, vertexes[3].y); + // ctx.lineTo(vertexes[0].x, vertexes[0].y); + // ctx.closePath(); + // ctx.stroke(); + // ctx.fill(); +} + +export function drawCrossVertexes( + ctx: ViewContext2D, + vertexes: ViewRectVertexes, + opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[] } +) { + const { borderColor, borderWidth, background, lineDash } = opts; + ctx.setLineDash([]); + ctx.lineWidth = borderWidth; + ctx.strokeStyle = borderColor; + ctx.fillStyle = background; + ctx.setLineDash(lineDash); + 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(); +} diff --git a/packages/core/src/middleware/selector/draw-wrapper.ts b/packages/core/src/middleware/selector/draw-wrapper.ts index 7fde75c..97bf0a4 100644 --- a/packages/core/src/middleware/selector/draw-wrapper.ts +++ b/packages/core/src/middleware/selector/draw-wrapper.ts @@ -11,93 +11,9 @@ import type { } from '@idraw/types'; import { rotateElementVertexes, calcViewPointSize, calcViewVertexes } from '@idraw/util'; import type { AreaSize } from './types'; - import { resizeControllerBorderWidth, areaBorderWidth, wrapperColor, selectWrapperBorderWidth, lockColor, controllerSize } from './config'; - -function drawVertexes( - ctx: ViewContext2D, - vertexes: ViewRectVertexes, - opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[] } -) { - const { borderColor, borderWidth, background, lineDash } = opts; - ctx.setLineDash([]); - ctx.lineWidth = borderWidth; - ctx.strokeStyle = borderColor; - ctx.fillStyle = background; - ctx.setLineDash(lineDash); - ctx.beginPath(); - ctx.moveTo(vertexes[0].x, vertexes[0].y); - ctx.lineTo(vertexes[1].x, vertexes[1].y); - ctx.lineTo(vertexes[2].x, vertexes[2].y); - ctx.lineTo(vertexes[3].x, vertexes[3].y); - ctx.lineTo(vertexes[0].x, vertexes[0].y); - ctx.closePath(); - ctx.stroke(); - ctx.fill(); -} - -function drawLine(ctx: ViewContext2D, start: PointSize, end: PointSize, opts: { borderColor: string; borderWidth: number; lineDash: number[] }) { - const { borderColor, borderWidth, lineDash } = opts; - ctx.setLineDash([]); - ctx.lineWidth = borderWidth; - ctx.strokeStyle = borderColor; - ctx.setLineDash(lineDash); - ctx.beginPath(); - ctx.moveTo(start.x, start.y); - ctx.lineTo(end.x, end.y); - ctx.closePath(); - ctx.stroke(); -} - -function drawCircleController( - ctx: ViewContext2D, - circleCenter: PointSize, - opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[]; size: number } -) { - const { size, borderColor, borderWidth, background } = opts; - const center = circleCenter; - const r = size / 2; - - const a = r; - const b = r; - // 'content-box' - - if (a >= 0 && b >= 0) { - // draw border - if (typeof borderWidth === 'number' && borderWidth > 0) { - const ba = borderWidth / 2 + a; - const bb = borderWidth / 2 + b; - ctx.beginPath(); - ctx.strokeStyle = borderColor; - ctx.lineWidth = borderWidth; - ctx.circle(center.x, center.y, ba, bb, 0, 0, 2 * Math.PI); - ctx.closePath(); - ctx.stroke(); - } - - // draw content - ctx.beginPath(); - ctx.fillStyle = background; - ctx.circle(center.x, center.y, a, b, 0, 0, 2 * Math.PI); - ctx.closePath(); - ctx.fill(); - } - - // ctx.setLineDash([]); - // ctx.lineWidth = borderWidth; - // ctx.strokeStyle = borderColor; - // ctx.fillStyle = background; - // ctx.setLineDash(lineDash); - // ctx.beginPath(); - // ctx.moveTo(vertexes[0].x, vertexes[0].y); - // ctx.lineTo(vertexes[1].x, vertexes[1].y); - // ctx.lineTo(vertexes[2].x, vertexes[2].y); - // ctx.lineTo(vertexes[3].x, vertexes[3].y); - // ctx.lineTo(vertexes[0].x, vertexes[0].y); - // ctx.closePath(); - // ctx.stroke(); - // ctx.fill(); -} +import { drawVertexes, drawLine, drawCircleController, drawCrossVertexes } from './draw-base'; +// import { drawSizeAuxiliaryLines } from './draw-auxiliary'; export function drawHoverVertexesWrapper( ctx: ViewContext2D, @@ -114,29 +30,6 @@ export function drawHoverVertexesWrapper( drawVertexes(ctx, calcViewVertexes(vertexes, opts), wrapperOpts); } -function drawCrossVertexes( - ctx: ViewContext2D, - vertexes: ViewRectVertexes, - opts: { borderColor: string; borderWidth: number; background: string; lineDash: number[] } -) { - const { borderColor, borderWidth, background, lineDash } = opts; - ctx.setLineDash([]); - ctx.lineWidth = borderWidth; - ctx.strokeStyle = borderColor; - ctx.fillStyle = background; - ctx.setLineDash(lineDash); - 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(); -} - export function drawLockVertexesWrapper( ctx: ViewContext2D, vertexes: ViewRectVertexes | null, @@ -172,26 +65,47 @@ export function drawLockVertexesWrapper( export function drawSelectedElementControllersVertexes( ctx: ViewContext2D, controller: ElementSizeController | null, - opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo } + opts: { + hideControllers: boolean; + viewScaleInfo: ViewScaleInfo; + viewSizeInfo: ViewSizeInfo; + element: Element | null; + groupQueue: Element<'group'>[]; + } ) { if (!controller) { return; } + const { hideControllers } = opts; + // const { element, groupQueue } = opts; const { elementWrapper, topLeft, topRight, bottomLeft, bottomRight, top, rotate } = controller; const wrapperOpts = { borderColor: wrapperColor, borderWidth: selectWrapperBorderWidth, background: 'transparent', lineDash: [] }; const ctrlOpts = { ...wrapperOpts, borderWidth: resizeControllerBorderWidth, background: '#FFFFFF' }; - drawLine(ctx, calcViewPointSize(top.center, opts), calcViewPointSize(rotate.center, opts), { ...ctrlOpts, borderWidth: 2 }); 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(topLeft.vertexes, opts), ctrlOpts); - drawVertexes(ctx, calcViewVertexes(topRight.vertexes, opts), ctrlOpts); - drawVertexes(ctx, calcViewVertexes(bottomLeft.vertexes, opts), ctrlOpts); - drawVertexes(ctx, calcViewVertexes(bottomRight.vertexes, opts), ctrlOpts); - drawCircleController(ctx, calcViewPointSize(rotate.center, opts), { ...ctrlOpts, size: controllerSize, borderWidth: 2 }); + if (!hideControllers) { + drawLine(ctx, calcViewPointSize(top.center, opts), calcViewPointSize(rotate.center, opts), { ...ctrlOpts, borderWidth: 2 }); + drawVertexes(ctx, calcViewVertexes(topLeft.vertexes, opts), ctrlOpts); + drawVertexes(ctx, calcViewVertexes(topRight.vertexes, opts), ctrlOpts); + drawVertexes(ctx, calcViewVertexes(bottomLeft.vertexes, opts), ctrlOpts); + drawVertexes(ctx, calcViewVertexes(bottomRight.vertexes, opts), ctrlOpts); + drawCircleController(ctx, calcViewPointSize(rotate.center, opts), { ...ctrlOpts, size: controllerSize, borderWidth: 2 }); + } + + // drawSizeAuxiliaryLines(ctx, { + // vertexes: [ + // calcViewPointSize(topLeft.center, opts), + // calcViewPointSize(topRight.center, opts), + // calcViewPointSize(bottomRight.center, opts), + // calcViewPointSize(bottomLeft.center, opts) + // ], + // element, + // groupQueue + // }); } export function drawElementListShadows(ctx: ViewContext2D, elements: Element[], opts?: Omit) { diff --git a/packages/core/src/middleware/selector/index.ts b/packages/core/src/middleware/selector/index.ts index 3f20b95..e749ef6 100644 --- a/packages/core/src/middleware/selector/index.ts +++ b/packages/core/src/middleware/selector/index.ts @@ -8,9 +8,10 @@ import { getGroupQueueFromList, findElementsFromList, findElementsFromListByPositions, + getElementPositionFromList, deepResizeGroupElement } from '@idraw/util'; -import type { ViewRectVertexes, CoreEvent, ElementPosition, ViewScaleInfo, ViewSizeInfo, ElementSizeController } from '@idraw/types'; +import type { ViewRectVertexes, CoreEventMap, ElementPosition, ViewScaleInfo, ViewSizeInfo, ElementSizeController } from '@idraw/types'; import type { Point, PointSize, @@ -53,6 +54,7 @@ import { keySelectedElementList, keySelectedElementListVertexes, keySelectedElementController, + keyIsMoving, controllerSize // keyDebugElemCenter, // keyDebugEnd0, @@ -67,7 +69,7 @@ export const middlewareEventSelect: string = '@middleware/select'; export const middlewareEventSelectClear: string = '@middleware/select-clear'; -export const MiddlewareSelector: BoardMiddleware = (opts) => { +export const MiddlewareSelector: BoardMiddleware = (opts) => { const { viewer, sharer, boardContent, calculator, eventHub } = opts; const { helperContext } = boardContent; let prevPoint: Point | null = null; @@ -154,6 +156,7 @@ export const MiddlewareSelector: BoardMiddleware { + sharer.setSharedStorage(keyIsMoving, true); const data = sharer.getActiveStorage('data'); const elems = getActiveElements(); const scale = sharer.getActiveStorage('scale') || 1; @@ -501,6 +505,8 @@ export const MiddlewareSelector: BoardMiddleware[] = sharedStore[keyGroupQueue]; const groupQueueVertexesList: ViewRectVertexes[] = sharedStore[keyGroupQueueVertexesList]; + const isMoving = sharedStore[keyIsMoving]; + const drawBaseOpts = { calculator, viewScaleInfo, viewSizeInfo }; // const selectedElementController = sharedStore[keySelectedElementController]; // const resizeType: ResizeType | null = sharedStore[keyResizeType]; + const selectedElementController = elem ? calcElementSizeController(elem, { groupQueue, @@ -656,7 +666,12 @@ export const MiddlewareSelector: BoardMiddleware>; [keySelectedElementListVertexes]: ViewRectVertexes | null; [keySelectedElementController]: ElementSizeController | null; + [keyIsMoving]: boolean | null; [keyDebugElemCenter]: PointSize | null; [keyDebugEnd0]: PointSize | null; diff --git a/packages/core/src/middleware/text-editor/index.ts b/packages/core/src/middleware/text-editor/index.ts index 1eea746..d74b584 100644 --- a/packages/core/src/middleware/text-editor/index.ts +++ b/packages/core/src/middleware/text-editor/index.ts @@ -1,23 +1,40 @@ -import type { BoardMiddleware, CoreEvent, Element, ElementSize, ViewScaleInfo } from '@idraw/types'; +import type { BoardMiddleware, CoreEventMap, Element, ElementSize, ViewScaleInfo, ElementPosition } from '@idraw/types'; import { limitAngle, getDefaultElementDetailConfig } from '@idraw/util'; export const middlewareEventTextEdit = '@middleware/text-edit'; +export const middlewareEventTextChange = '@middleware/text-change'; type TextEditEvent = { element: Element<'text'>; + position: ElementPosition; groupQueue: Element<'group'>[]; viewScaleInfo: ViewScaleInfo; }; +type TextChangeEvent = { + element: { + uuid: string; + detail: { + text: string; + }; + }; + position: ElementPosition; +}; + +type ExtendEventMap = Record & Record; + const defaultElementDetail = getDefaultElementDetailConfig(); -export const MiddlewareTextEditor: BoardMiddleware, CoreEvent> = (opts) => { +export const MiddlewareTextEditor: BoardMiddleware = (opts) => { const { eventHub, boardContent, viewer } = opts; const canvas = boardContent.boardContext.canvas; - const textarea = document.createElement('textarea'); + // const textarea = document.createElement('textarea'); + const textarea = document.createElement('div'); + textarea.setAttribute('contenteditable', 'true'); const canvasWrapper = document.createElement('div'); const container = opts.container || document.body; const mask = document.createElement('div'); let activeElem: Element<'text'> | null = null; + let activePosition: ElementPosition = []; canvasWrapper.appendChild(textarea); @@ -41,6 +58,7 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven const hideTextArea = () => { mask.style.display = 'none'; activeElem = null; + activePosition = []; }; const getCanvasRect = () => { @@ -109,6 +127,24 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven elemH = element.h * scale; } + let justifyContent: ElementCSSInlineStyle['style']['justifyContent'] = 'center'; + let alignItems = 'center'; + if (detail.textAlign === 'left') { + justifyContent = 'start'; + } else if (detail.textAlign === 'right') { + justifyContent = 'end'; + } + + if (detail.verticalAlign === 'top') { + alignItems = 'start'; + } else if (detail.verticalAlign === 'bottom') { + alignItems = 'end'; + } + + textarea.style.display = 'inline-flex'; + textarea.style.justifyContent = justifyContent; + textarea.style.alignItems = alignItems; + textarea.style.position = 'absolute'; textarea.style.left = `${elemX - 1}px`; textarea.style.top = `${elemY - 1}px`; @@ -131,7 +167,8 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven textarea.style.margin = '0'; textarea.style.outline = 'none'; - textarea.value = detail.text || ''; + // textarea.value = detail.text || ''; + textarea.innerText = detail.text || ''; parent.appendChild(textarea); }; @@ -152,19 +189,42 @@ export const MiddlewareTextEditor: BoardMiddleware, CoreEven textarea.addEventListener('click', (e) => { e.stopPropagation(); }); - textarea.addEventListener('input', (e) => { - if (activeElem) { - activeElem.detail.text = (e.target as any).value || ''; + textarea.addEventListener('input', () => { + if (activeElem && activePosition) { + // activeElem.detail.text = (e.target as any).value || ''; + activeElem.detail.text = textarea.innerText || ''; + eventHub.trigger(middlewareEventTextChange, { + element: { + uuid: activeElem.uuid, + detail: { + text: activeElem.detail.text + } + }, + position: [...(activePosition || [])] + }); viewer.drawFrame(); } }); textarea.addEventListener('blur', () => { + if (activeElem && activePosition) { + eventHub.trigger(middlewareEventTextChange, { + element: { + uuid: activeElem.uuid, + detail: { + text: activeElem.detail.text + } + }, + position: [...activePosition] + }); + } + hideTextArea(); }); const textEditCallback = (e: TextEditEvent) => { - if (e?.element && e?.element?.type === 'text') { + if (e?.position && e?.element && e?.element?.type === 'text') { activeElem = e.element; + activePosition = e.position; } showTextArea(e); }; diff --git a/packages/idraw/src/event.ts b/packages/idraw/src/event.ts index d613d71..35d607e 100644 --- a/packages/idraw/src/event.ts +++ b/packages/idraw/src/event.ts @@ -1,32 +1,49 @@ -import { middlewareEventScale, middlewareEventSelect } from '@idraw/core'; -import type { CoreEvent, Data } from '@idraw/types'; +import type { CoreEventMap, Data } from '@idraw/types'; -export interface IDrawEventKeys { - select: typeof middlewareEventSelect; - scale: typeof middlewareEventScale; - change: 'change'; -} +import { + middlewareEventRuler, + middlewareEventScale, + middlewareEventSelect, + middlewareEventSelectClear, + middlewareEventTextEdit, + middlewareEventTextChange +} from '@idraw/core'; -export type IDrawEvent = CoreEvent & { +const idrawEventChange = 'change'; + +export type IDrawEvent = CoreEventMap & { change: { data: Data; type: 'updateElement' | 'deleteElement' | 'moveElement' | 'addElement' | 'dragElement' | 'resizeElement' | 'setData' | 'undo' | 'redo' | 'other'; }; }; -// TODO -const EventKeys = {} as { - select: typeof middlewareEventSelect; +export interface IDrawEventKeys { + change: typeof idrawEventChange; + ruler: typeof middlewareEventRuler; scale: typeof middlewareEventScale; - change: 'change'; + select: typeof middlewareEventSelect; + clearSelect: typeof middlewareEventSelectClear; + textEdit: typeof middlewareEventTextEdit; + textChange: typeof middlewareEventTextChange; +} + +const innerEventKeys: IDrawEventKeys = { + change: idrawEventChange, + ruler: middlewareEventRuler, + scale: middlewareEventScale, + select: middlewareEventSelect, + clearSelect: middlewareEventSelectClear, + textEdit: middlewareEventTextEdit, + textChange: middlewareEventTextChange }; -Object.defineProperty(EventKeys, 'select', { - value: middlewareEventSelect, - writable: false -}); -Object.defineProperty(EventKeys, 'scale', { - value: middlewareEventScale, - writable: false + +const eventKeys = {} as IDrawEventKeys; +Object.keys(innerEventKeys).forEach((keyName: string) => { + Object.defineProperty(eventKeys, keyName, { + value: innerEventKeys[keyName as keyof IDrawEventKeys], + writable: false + }); }); -export { EventKeys }; +export { eventKeys }; diff --git a/packages/idraw/src/idraw.ts b/packages/idraw/src/idraw.ts index 9550438..a0bb877 100644 --- a/packages/idraw/src/idraw.ts +++ b/packages/idraw/src/idraw.ts @@ -1,13 +1,4 @@ -import { - Core, - MiddlewareSelector, - MiddlewareScroller, - MiddlewareScaler, - MiddlewareRuler, - MiddlewareTextEditor, - middlewareEventSelect, - MiddlewareDragger -} from '@idraw/core'; +import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler, MiddlewareTextEditor, MiddlewareDragger } from '@idraw/core'; import type { PointSize, IDrawOptions, @@ -36,6 +27,7 @@ import { import { defaultSettings } from './config'; import { exportImageFileBlobURL } from './file'; import type { ExportImageFileBaseOptions, ExportImageFileResult } from './file'; +import { eventKeys } from './event'; export class iDraw { #core: Core; @@ -111,7 +103,7 @@ export class iDraw { setData(data: Data) { const core = this.#core; core.setData(data); - core.trigger('change', { data, type: 'setData' }); + core.trigger(eventKeys.change, { data, type: 'setData' }); } getData(opts?: { compact?: boolean }): Data | null { @@ -171,7 +163,7 @@ export class iDraw { } selectElements(uuids: string[]) { - this.trigger(middlewareEventSelect, { uuids }); + this.trigger(eventKeys.select, { uuids }); } selectElementByPosition(position: ElementPosition) { @@ -179,11 +171,11 @@ export class iDraw { } selectElementsByPositions(positions: ElementPosition[]) { - this.trigger(middlewareEventSelect, { positions }); + this.trigger(eventKeys.select, { positions }); } cancelElements() { - this.trigger(middlewareEventSelect, { uuids: [] }); + this.trigger(eventKeys.select, { uuids: [] }); } createElement( @@ -212,7 +204,7 @@ export class iDraw { updateElementInList(element.uuid, element, data.elements); core.setData(data); core.refresh(); - core.trigger('change', { data, type: 'updateElement' }); + core.trigger(eventKeys.change, { data, type: 'updateElement' }); } addElement( @@ -231,7 +223,7 @@ export class iDraw { } core.setData(data); core.refresh(); - core.trigger('change', { data, type: 'addElement' }); + core.trigger(eventKeys.change, { data, type: 'addElement' }); return data; } @@ -241,7 +233,7 @@ export class iDraw { deleteElementInList(uuid, data.elements); core.setData(data); core.refresh(); - core.trigger('change', { data, type: 'deleteElement' }); + core.trigger(eventKeys.change, { data, type: 'deleteElement' }); } moveElement(uuid: string, to: ElementPosition) { @@ -252,7 +244,7 @@ export class iDraw { data.elements = list; core.setData(data); core.refresh(); - core.trigger('change', { data, type: 'moveElement' }); + core.trigger(eventKeys.change, { data, type: 'moveElement' }); } async getImageBlobURL(opts: ExportImageFileBaseOptions): Promise { diff --git a/packages/idraw/src/index.ts b/packages/idraw/src/index.ts index 1175862..1218167 100644 --- a/packages/idraw/src/index.ts +++ b/packages/idraw/src/index.ts @@ -121,5 +121,6 @@ export { modifyElement } from '@idraw/util'; export { iDraw } from './idraw'; +export { eventKeys } from './event'; export type { IDrawEvent, IDrawEventKeys } from './event'; export type { ExportImageFileResult, ExportImageFileBaseOptions } from './file'; diff --git a/packages/renderer/src/draw/box.ts b/packages/renderer/src/draw/box.ts index c259448..6c8f26b 100644 --- a/packages/renderer/src/draw/box.ts +++ b/packages/renderer/src/draw/box.ts @@ -314,6 +314,12 @@ export function drawBoxShadow( renderContent(); ctx.restore(); } else { + ctx.save(); + ctx.shadowColor = 'transparent'; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 0; renderContent(); + ctx.restore(); } } diff --git a/packages/renderer/src/draw/circle.ts b/packages/renderer/src/draw/circle.ts index aa5b48b..88c9db4 100644 --- a/packages/renderer/src/draw/circle.ts +++ b/packages/renderer/src/draw/circle.ts @@ -5,6 +5,7 @@ import { drawBoxShadow, getOpacity } from './box'; export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: RendererDrawElementOptions) { const { detail, angle } = elem; + const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts; const { background = '#000000', borderColor = '#000000', boxSizing, borderWidth = 0 } = detail; let bw: number = 0; if (typeof borderWidth === 'number' && borderWidth > 0) { @@ -12,7 +13,8 @@ export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: Re } else if (Array.isArray(borderWidth) && typeof borderWidth[0] === 'number' && borderWidth[0] > 0) { bw = borderWidth[0] as number; } - const { calculator, viewScaleInfo, viewSizeInfo, parentOpacity } = opts; + bw = bw * viewScaleInfo.scale; + // const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = viewScaleInfo; const { x, y, w, h } = calculator?.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, viewScaleInfo, viewSizeInfo) || elem; const viewElem = { ...elem, ...{ x, y, w, h, angle } }; @@ -46,12 +48,12 @@ export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: Re ctx.globalAlpha = opacity; // draw border - if (typeof borderWidth === 'number' && borderWidth > 0) { - const ba = borderWidth / 2 + a; - const bb = borderWidth / 2 + b; + if (typeof bw === 'number' && bw > 0) { + const ba = bw / 2 + a; + const bb = bw / 2 + b; ctx.beginPath(); ctx.strokeStyle = borderColor; - ctx.lineWidth = borderWidth; + ctx.lineWidth = bw; ctx.circle(centerX, centerY, ba, bb, 0, 0, 2 * Math.PI); ctx.closePath(); ctx.stroke(); diff --git a/packages/renderer/src/index.ts b/packages/renderer/src/index.ts index 54e65fc..0e94ca7 100644 --- a/packages/renderer/src/index.ts +++ b/packages/renderer/src/index.ts @@ -32,8 +32,10 @@ export class Renderer extends EventEmitter implements BoardRen loader.on('load', (e) => { this.trigger('load', e); }); - loader.on('error', () => { + loader.on('error', (e) => { // TODO + // eslint-disable-next-line no-console + console.error(e); }); } diff --git a/packages/renderer/src/loader.ts b/packages/renderer/src/loader.ts index 4920caa..649e5e0 100644 --- a/packages/renderer/src/loader.ts +++ b/packages/renderer/src/loader.ts @@ -138,6 +138,9 @@ export class Loader extends EventEmitter implements RendererLoad #loadResource(element: Element, assets: ElementAssets) { const item = this.#createLoadItem(element); const assetId = getAssetIdFromElement(element); + if (this.#currentLoadItemMap[assetId]) { + return; + } this.#currentLoadItemMap[assetId] = item; const loadFunc = this.#loadFuncMap[element.type]; diff --git a/packages/types/src/lib/board.ts b/packages/types/src/lib/board.ts index dcb43a6..142c5e6 100644 --- a/packages/types/src/lib/board.ts +++ b/packages/types/src/lib/board.ts @@ -5,6 +5,12 @@ import type { ActiveStore, StoreSharer } from './store'; import type { RendererEventMap, RendererOptions, RendererDrawOptions, RendererLoader } from './renderer'; import type { Data } from './data'; +export type BoardBaseEventMap = { + loadSource: void; +}; + +export type BoardExtendEventMap = BoardBaseEventMap & Record; + export interface BoardWatcherPointEvent { point: Point; } @@ -81,7 +87,7 @@ export interface BoardMiddlewareObject = any clear?(e: BoardWatcherEventMap['clear']): void | boolean; } -export interface BoardMiddlewareOptions = Record, E extends BoardExtendEvent = Record> { +export interface BoardMiddlewareOptions = Record, E extends BoardExtendEventMap = BoardExtendEventMap> { boardContent: BoardContent; sharer: StoreSharer; viewer: BoardViewer; @@ -91,7 +97,7 @@ export interface BoardMiddlewareOptions = Re canvas?: HTMLCanvasElement; } -export type BoardMiddleware = any, E extends BoardExtendEvent = Record> = ( +export type BoardMiddleware = any, E extends BoardExtendEventMap = BoardExtendEventMap> = ( opts: BoardMiddlewareOptions ) => BoardMiddlewareObject; @@ -147,5 +153,3 @@ export interface BoardWatcherStore { hasPointDown: boolean; prevClickPoint: Point | null; } - -export type BoardExtendEvent = Record; diff --git a/packages/types/src/lib/context2d.ts b/packages/types/src/lib/context2d.ts index 6a8806b..f9ed7c1 100644 --- a/packages/types/src/lib/context2d.ts +++ b/packages/types/src/lib/context2d.ts @@ -9,6 +9,7 @@ export interface ViewContext2D { // extend API $getContext(): CanvasRenderingContext2D; $setContext(ctx: CanvasRenderingContext2D): void; + $resetFont(): void; $setFont(opts: { fontSize: number; fontFamily?: string; fontWeight?: string | number }): void; $resize(opts: { width: number; height: number; devicePixelRatio: number }): void; $getSize(): { width: number; height: number; devicePixelRatio: number }; diff --git a/packages/types/src/lib/core.ts b/packages/types/src/lib/core.ts index 2e12368..922db13 100644 --- a/packages/types/src/lib/core.ts +++ b/packages/types/src/lib/core.ts @@ -1,6 +1,7 @@ import type { Element, ElementType } from './element'; import type { Data } from './data'; import type { ViewContext2D } from './context2d'; +import type { BoardBaseEventMap } from './board'; export interface CoreOptions { width: number; @@ -40,7 +41,7 @@ export interface CoreEventScale { scale: number; } -export type CoreEvent = { +export type CoreEventMap = BoardBaseEventMap & { cursor: CoreEventCursor; change: CoreEventChange; [key: string]: any; diff --git a/packages/types/src/lib/middleware.ts b/packages/types/src/lib/middleware.ts index 02e7acb..d0a5491 100644 --- a/packages/types/src/lib/middleware.ts +++ b/packages/types/src/lib/middleware.ts @@ -1,7 +1,5 @@ -import type { BoardMiddlewareObject, BoardMiddleware, BoardMode } from './board'; +import type { BoardMiddlewareObject, BoardMiddleware } from './board'; export type Middleware = BoardMiddleware; export type MiddlewareObject = BoardMiddlewareObject; - -export type MiddlewareMode = BoardMode; diff --git a/packages/types/src/lib/store.ts b/packages/types/src/lib/store.ts index e641b79..647bc6c 100644 --- a/packages/types/src/lib/store.ts +++ b/packages/types/src/lib/store.ts @@ -1,9 +1,14 @@ import { Data } from './data'; -import { ViewScaleInfo, ViewSizeInfo } from './view'; +import { + // ViewRectVertexes, + ViewScaleInfo, + ViewSizeInfo +} from './view'; export type ActiveStore = ViewSizeInfo & ViewScaleInfo & { data: Data | null; + // selectedViewRectVertexes: ViewRectVertexes | null; }; export interface StoreSharer = any> { diff --git a/packages/util/src/lib/context2d.ts b/packages/util/src/lib/context2d.ts index 51f8c69..d1c96db 100644 --- a/packages/util/src/lib/context2d.ts +++ b/packages/util/src/lib/context2d.ts @@ -1,5 +1,9 @@ import type { ViewContext2D, ViewContext2DOptions } from '@idraw/types'; +const defaultFontSize = 12; +const defaultFontWeight = '400'; +const defaultFontFamily = `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'`; + export class Context2D implements ViewContext2D { #ctx: CanvasRenderingContext2D; #opts: Required; @@ -12,6 +16,7 @@ export class Context2D implements ViewContext2D { this.#opts = { ...{ devicePixelRatio: 1, offscreenCanvas: null }, ...opts }; // this._width = ctx.canvas.width / devicePixelRatio; // this._height = ctx.canvas.height / devicePixelRatio; + this.$resetFont(); } $undoPixelRatio(num: number) { @@ -39,6 +44,14 @@ export class Context2D implements ViewContext2D { this.#ctx.font = `${strList.join(' ')}`; } + $resetFont() { + this.$setFont({ + fontSize: defaultFontSize, + fontFamily: defaultFontFamily, + fontWeight: defaultFontWeight + }); + } + $getOffscreenCanvas(): OffscreenCanvas | null { return this.#opts.offscreenCanvas; }