feat: add middleware of layout selector

This commit is contained in:
chenshenhai 2024-03-30 10:22:21 +08:00
parent 9f4ba90090
commit c0de3034ec
20 changed files with 626 additions and 27 deletions

View file

@ -0,0 +1 @@
export const eventChange = 'change';

View file

@ -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<E extends CoreEventMap = CoreEventMap> {
#board: Board<E>;

View file

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

View file

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

View file

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

View file

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

View file

@ -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<DeepSelectorSharedStorage, Core
triggerCursor(target);
if (target.type === null) {
if (sharer.getSharedStorage(keyHoverElement) || sharer.getSharedStorage(keyHoverElementVertexes)) {
sharer.setSharedStorage(keyHoverElement, null);
sharer.setSharedStorage(keyHoverElementVertexes, null);
viewer.drawFrame();
}
return;
}
@ -314,7 +320,6 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
}
if (target.type === 'over-element' && target?.elements?.length === 1) {
// sharer.setSharedStorage(keyHoverElement, target.elements[0]);
updateHoverElement(target.elements[0]);
viewer.drawFrame();
return;
@ -580,7 +585,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
prevPoint = e.point;
// if (data && (['drag', 'drag-list', 'drag-list-end', 'resize'] as ActionType[]).includes(actionType)) {
// eventHub.trigger('change', { data });
// eventHub.trigger(eventChange, { data });
// }
},
@ -657,7 +662,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
if (type === 'resize') {
type = 'resizeElement';
}
eventHub.trigger('change', { data, type });
eventHub.trigger(eventChange, { data, type });
}
viewer.drawFrame();
};

View file

@ -1,4 +1,5 @@
import type { CoreEventMap, Data } from '@idraw/types';
import { eventChange } from '@idraw/core';
import {
middlewareEventRuler,
@ -9,12 +10,23 @@ import {
middlewareEventTextChange
} from '@idraw/core';
const idrawEventChange = 'change';
const idrawEventChange = eventChange;
export type IDrawEvent = CoreEventMap & {
change: {
[idrawEventChange]: {
data: Data;
type: 'updateElement' | 'deleteElement' | 'moveElement' | 'addElement' | 'dragElement' | 'resizeElement' | 'setData' | 'undo' | 'redo' | 'other';
type:
| 'updateElement'
| 'deleteElement'
| 'moveElement'
| 'addElement'
| 'dragElement'
| 'resizeElement'
| 'setData'
| 'undo'
| 'redo'
| 'changeLayout' // TODO
| 'other';
};
};

View file

@ -134,7 +134,7 @@ export class iDraw {
centerContent(opts?: { data?: Data }) {
const data = opts?.data || this.#core.getData();
const { viewSizeInfo } = this.getViewInfo();
if (Array.isArray(data?.elements) && data?.elements.length > 0) {
if (data?.layout || (Array.isArray(data?.elements) && data?.elements.length > 0)) {
const result = calcViewCenterContent(data, { viewSizeInfo });
this.setViewScale(result);
}

View file

@ -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<IDrawEvent>, store: Store<IDrawStorage
}
if (enableSelect === true) {
core.use(MiddlewareLayoutSelector);
core.use(MiddlewareSelector);
} else if (enableSelect === false) {
core.disuse(MiddlewareLayoutSelector);
core.disuse(MiddlewareSelector);
}

View file

@ -4,7 +4,7 @@ import { drawBoxShadow, drawBoxBackground, drawBoxBorder } from './box';
export function drawLayout(ctx: ViewContext2D, layout: DataLayout, opts: RendererDrawElementOptions, renderContent: (ctx: ViewContext2D) => void) {
const { viewScaleInfo, viewSizeInfo, parentOpacity } = opts;
const elem: Element = { uuid: 'layout', type: 'group', x: 0, y: 0, ...layout };
const 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, {

View file

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

View file

@ -1,5 +1,5 @@
import type { ElementBaseDetail, ElementTextDetail, ElementGroupDetail } from './element';
export type DefaultElementDetailConfig = Required<Omit<ElementBaseDetail, 'clipPath' | 'background'>> &
Required<Pick<ElementTextDetail, 'color' | 'textAlign' | 'verticalAlign' | 'fontSize' | 'fontFamily' | 'fontWeight' | 'lineHeight'>> &
Required<Pick<ElementTextDetail, 'color' | 'textAlign' | 'verticalAlign' | 'fontSize' | 'fontFamily' | 'fontWeight'>> &
Required<Pick<ElementGroupDetail, 'overflow'>>;

View file

@ -38,3 +38,5 @@ export interface ElementSizeController {
rightMiddle: ElementSizeControllerItem;
rotate: ElementSizeControllerItem;
}
export type LayoutSizeController = Omit<ElementSizeController, 'rotate' | 'elementWrapper'>;

View file

@ -1,7 +1,20 @@
import type { Element, ElementType, ElementAssets, ElementSize, ElementGroupDetail } from './element';
export type DataLayout = Pick<ElementSize, 'w' | 'h'> & {
detail: Omit<ElementGroupDetail, 'children'>;
export type DataLayout = Pick<ElementSize, 'x' | 'y' | 'w' | 'h'> & {
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<E extends Record<string, any> = Record<string, any>> {
elements: Element<ElementType, E>[];

View file

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

View file

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

View file

@ -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<ElementSize, 'x' | 'y' | 'w' | 'h'>,
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;
}

View file

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

View file

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