feat: improve renderer and middleware

This commit is contained in:
chenshenhai 2024-06-08 18:47:51 +08:00
parent 6786f97db6
commit ac60ce4bad
12 changed files with 114 additions and 83 deletions

View file

@ -5,7 +5,13 @@ import { Cursor } from './lib/cursor';
export { eventChange } from './config';
// export { MiddlewareSelector } from './middleware/selector';
export { MiddlewareSelector, middlewareEventSelect, middlewareEventSelectClear, middlewareEventSelectInGroup } from './middleware/selector';
export {
MiddlewareSelector,
middlewareEventSelect,
middlewareEventSelectClear,
middlewareEventSelectInGroup,
middlewareEventSnapToGrid
} from './middleware/selector';
export { MiddlewareScroller } from './middleware/scroller';
export { MiddlewareScaler, middlewareEventScale } from './middleware/scaler';
export { MiddlewareRuler, middlewareEventRuler } from './middleware/ruler';

View file

@ -16,7 +16,8 @@ export const keySelectedReferenceYLines = Symbol(`${key}_selectedReferenceYLines
export const keyGroupQueue = Symbol(`${key}_groupQueue`); // Array<Element<'group'>> | []
export const keyGroupQueueVertexesList = Symbol(`${key}_groupQueueVertexesList`); // Array<ViewRectVertexes> | []
export const keyIsMoving = Symbol(`${key}_isMoving`); // boolean | null
export const keyEnableSelectInGroup = Symbol(`${key}_canSelectInGroup`);
export const keyEnableSelectInGroup = Symbol(`${key}_enableSelectInGroup`);
export const keyEnableSnapToGrid = Symbol(`${key}_enableSnapToGrid`);
export const keyDebugElemCenter = Symbol(`${key}_debug_elemCenter`);
export const keyDebugStartVertical = Symbol(`${key}_debug_startVertical`);
@ -44,3 +45,5 @@ export const middlewareEventSelect: string = '@middleware/select';
export const middlewareEventSelectClear: string = '@middleware/select-clear';
export const middlewareEventSelectInGroup: string = '@middleware/select-in-group';
export const middlewareEventSnapToGrid: string = '@middleware/snap-to-grid';

View file

@ -48,6 +48,7 @@ import {
middlewareEventSelect,
middlewareEventSelectClear,
middlewareEventSelectInGroup,
middlewareEventSnapToGrid,
keyActionType,
keyResizeType,
keyAreaStart,
@ -64,6 +65,7 @@ import {
keySelectedReferenceYLines,
keyIsMoving,
keyEnableSelectInGroup,
keyEnableSnapToGrid,
controllerSize
// keyDebugElemCenter,
// keyDebugEnd0,
@ -79,7 +81,7 @@ import { eventChange } from '../../config';
export { keySelectedElementList, keyActionType, keyResizeType, keyGroupQueue };
export type { DeepSelectorSharedStorage, ActionType };
export { middlewareEventSelect, middlewareEventSelectClear, middlewareEventSelectInGroup };
export { middlewareEventSelect, middlewareEventSelectClear, middlewareEventSelectInGroup, middlewareEventSnapToGrid };
export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, CoreEventMap> = (opts) => {
const { viewer, sharer, boardContent, calculator, eventHub } = opts;
@ -88,6 +90,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
let inBusyMode: 'resize' | 'drag' | 'drag-list' | 'area' | null = null;
sharer.setSharedStorage(keyActionType, null);
sharer.setSharedStorage(keyEnableSnapToGrid, true);
const getActiveElements = () => {
return sharer.getSharedStorage(keySelectedElementList);
@ -213,6 +216,10 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
viewer.drawFrame();
};
const setSnapToSnapCallback = (e: { enable: boolean }) => {
sharer.setSharedStorage(keyEnableSnapToGrid, !!e.enable);
};
const selectInGroupCallback = (e: { enable: boolean }) => {
sharer.setSharedStorage(keyEnableSelectInGroup, !!e.enable);
};
@ -223,12 +230,14 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
eventHub.on(middlewareEventSelect, selectCallback);
eventHub.on(middlewareEventSelectClear, selectClearCallback);
eventHub.on(middlewareEventSelectInGroup, selectInGroupCallback);
eventHub.on(middlewareEventSnapToGrid, setSnapToSnapCallback);
},
disuse() {
eventHub.off(middlewareEventSelect, selectCallback);
eventHub.off(middlewareEventSelectClear, selectClearCallback);
eventHub.off(middlewareEventSelectInGroup, selectInGroupCallback);
eventHub.off(middlewareEventSnapToGrid, setSnapToSnapCallback);
},
hover: (e: PointWatcherEvent) => {
@ -435,6 +444,8 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
const actionType = sharer.getSharedStorage(keyActionType);
const groupQueue = sharer.getSharedStorage(keyGroupQueue);
const enableSnapToGrid = sharer.getSharedStorage(keyEnableSnapToGrid);
if (actionType === 'drag') {
inBusyMode = 'drag';
if (data && elems?.length === 1 && start && end && elems[0]?.operations?.lock !== true) {
@ -443,27 +454,29 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
let totalMoveX = calculator.toGridNum(moveX / scale);
let totalMoveY = calculator.toGridNum(moveY / scale);
const referenceInfo = calcReferenceInfo(elems[0].uuid, {
calculator,
data,
groupQueue,
viewScaleInfo,
viewSizeInfo
});
try {
if (referenceInfo) {
if (is.x(referenceInfo.offsetX) && referenceInfo.offsetX !== null) {
totalMoveX = calculator.toGridNum(totalMoveX + referenceInfo.offsetX);
if (enableSnapToGrid === true) {
const referenceInfo = calcReferenceInfo(elems[0].uuid, {
calculator,
data,
groupQueue,
viewScaleInfo,
viewSizeInfo
});
try {
if (referenceInfo) {
if (is.x(referenceInfo.offsetX) && referenceInfo.offsetX !== null) {
totalMoveX = calculator.toGridNum(totalMoveX + referenceInfo.offsetX);
}
if (is.y(referenceInfo.offsetY) && referenceInfo.offsetY !== null) {
totalMoveY = calculator.toGridNum(totalMoveY + referenceInfo.offsetY);
}
sharer.setSharedStorage(keySelectedReferenceXLines, referenceInfo.xLines);
sharer.setSharedStorage(keySelectedReferenceYLines, referenceInfo.yLines);
}
if (is.y(referenceInfo.offsetY) && referenceInfo.offsetY !== null) {
totalMoveY = calculator.toGridNum(totalMoveY + referenceInfo.offsetY);
}
sharer.setSharedStorage(keySelectedReferenceXLines, referenceInfo.xLines);
sharer.setSharedStorage(keySelectedReferenceYLines, referenceInfo.yLines);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
elems[0].x = calculator.toGridNum(elems[0].x + totalMoveX);
@ -733,6 +746,7 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
const groupQueue: Element<'group'>[] = sharedStore[keyGroupQueue];
const groupQueueVertexesList: ViewRectVertexes[] = sharedStore[keyGroupQueueVertexesList];
const isMoving = sharedStore[keyIsMoving];
const enableSnapToGrid = sharedStore[keyEnableSnapToGrid];
const drawBaseOpts = { calculator, viewScaleInfo, viewSizeInfo };
// const selectedElementController = sharedStore[keySelectedElementController];
@ -775,10 +789,12 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
if (actionType === 'drag') {
const xLines = sharer.getSharedStorage(keySelectedReferenceXLines);
const yLines = sharer.getSharedStorage(keySelectedReferenceYLines);
drawReferenceLines(overlayContext, {
xLines,
yLines
});
if (enableSnapToGrid === true) {
drawReferenceLines(overlayContext, {
xLines,
yLines
});
}
}
}
} else {
@ -807,10 +823,12 @@ export const MiddlewareSelector: BoardMiddleware<DeepSelectorSharedStorage, Core
if (actionType === 'drag') {
const xLines = sharer.getSharedStorage(keySelectedReferenceXLines);
const yLines = sharer.getSharedStorage(keySelectedReferenceYLines);
drawReferenceLines(overlayContext, {
xLines,
yLines
});
if (enableSnapToGrid === true) {
drawReferenceLines(overlayContext, {
xLines,
yLines
});
}
}
} else if (actionType === 'area' && areaStart && areaEnd) {
drawArea(overlayContext, { start: areaStart, end: areaEnd });

View file

@ -32,6 +32,7 @@ import {
keySelectedReferenceYLines,
keyIsMoving,
keyEnableSelectInGroup,
keyEnableSnapToGrid,
// debug keys
keyDebugElemCenter,
@ -107,6 +108,7 @@ export type DeepSelectorSharedStorage = {
[keySelectedReferenceYLines]: Array<PointSize[]>;
[keyIsMoving]: boolean | null;
[keyEnableSelectInGroup]: boolean | null;
[keyEnableSnapToGrid]: boolean | null;
[keyDebugElemCenter]: PointSize | null;
[keyDebugEnd0]: PointSize | null;

View file

@ -25,53 +25,14 @@ async function action(params: { data: Data }) {
idraw.centerContent();
}
// async function main() {
// if (targetFile) {
// const filePath = `/demo/lab-figma-to-elements/figma/${targetFile}`;
// const figma = await fetch(filePath).then((res) => res.blob());
// const buffer = await figma.arrayBuffer();
// {
// const filePath = `/demo/lab-figma-to-elements/figma/${targetFile}`;
// const figma = await fetch(filePath).then((res) => res.blob());
// const buff = await figma.arrayBuffer();
// const bytes = new Uint8Array(buff);
// const figmaMap = await figmaBytesToMap(bytes);
// console.log('figmaMap ===== ', figmaMap);
// const data = await figmaMapToIDrawData(figmaMap);
// console.log('object ===== ', data);
// }
// let data = await figmaBufferToIDrawData(buffer);
// // console.log('object ====== ', object);
// // const map = figmaObjectToMap(object);
// // console.log('map ==== ', map);
// // const tree = figmaObjectToTree(object);
// // console.log('tree ==== ', tree);
// // let data = figmaObjectToIDrawData(object);
// // TODO
// data = {
// elements: (data.elements[0] as Element<'group'>).detail.children
// };
// // console.log('data ===== ', data);
// await action({ data });
// } else {
// list();
// }
// }
async function main() {
const filePath = `/dev/figma/iOS-Native-Wireframes-Community.fig`;
console.log('filePath ------ ', filePath);
const figma = await fetch(filePath).then((res) => res.blob());
const arrayBuffer = await figma.arrayBuffer();
const buffer = new Uint8Array(arrayBuffer);
let data: Data = await figmaBytesToIDrawData(buffer);
// // TODO
data = {
elements: (data.elements[0] as Element<'group'>).detail.children,
global: data.elements[0].global

View file

@ -1,4 +1,4 @@
import { Core, middlewareEventSelectInGroup } from '@idraw/core';
import { Core, middlewareEventSelectInGroup, middlewareEventSnapToGrid } from '@idraw/core';
import type {
PointSize,
IDrawOptions,
@ -72,6 +72,10 @@ export class iDraw {
this.#core.trigger(middlewareEventSelectInGroup, {
enable: !!status
});
} else if (feat === 'snapToGrid') {
this.#core.trigger(middlewareEventSnapToGrid, {
enable: !!status
});
}
}

View file

@ -112,6 +112,5 @@ export function changeMode(mode: IDrawMode, core: Core<IDrawEvent>, store: Store
store.set('enableDrag', enableDrag);
store.set('enableRuler', enableRuler);
store.set('enableInfo', enableInfo);
runMiddlewares(core, store);
}

View file

@ -9,13 +9,15 @@ import { drawHTML } from './html';
import { drawBox, drawBoxShadow, getOpacity } from './box';
import { drawPath } from './path';
const visiableMinSize = 0.4; // px;
export function drawElement(ctx: ViewContext2D, elem: Element<ElementType>, opts: RendererDrawElementOptions) {
if (elem?.operations?.invisible === true) {
return;
}
const { w, h } = elem;
const { scale } = opts.viewScaleInfo;
if ((scale < 1 && (w * scale < 1 || h * scale < 1)) || opts.parentOpacity === 0) {
if ((scale < 1 && (w * scale < visiableMinSize || h * scale < visiableMinSize)) || opts.parentOpacity === 0) {
return;
}

View file

@ -1,4 +1,4 @@
import type { Element, RendererDrawElementOptions, ViewContext2D } from '@idraw/types';
import type { Element, RendererDrawElementOptions, ViewContext2D, LinearGradientColor, RadialGradientColor } from '@idraw/types';
import { rotateElement, generateSVGPath, calcViewElementSize } from '@idraw/util';
import { drawBox, drawBoxShadow } from './box';
@ -13,13 +13,41 @@ export function drawPath(ctx: ViewContext2D, elem: Element<'path'>, opts: Render
const viewOriginY = originY * scaleH;
const internalX = x - viewOriginX;
const internalY = y - viewOriginY;
const { clipPath, clipPathStrokeColor, clipPathStrokeWidth, ...restDetail } = elem.detail as any;
const scaleNum = viewScaleInfo.scale * viewSizeInfo.devicePixelRatio;
const viewElem = { ...elem, ...{ x, y, w, h, angle } };
// rotateElement(ctx, { x: viewOriginX, y: viewOriginY, w, h, angle }, () => {
let boxViewElem = { ...viewElem };
boxViewElem.detail = restDetail;
let boxOriginElem = { ...elem };
boxOriginElem.detail = restDetail;
if (detail.fill && detail.fill !== 'string' && (detail.fill as LinearGradientColor | RadialGradientColor)?.type?.includes('gradient')) {
boxViewElem = {
...viewElem,
...{
detail: {
...viewElem.detail,
...{
background: detail.fill,
clipPath: {
commands: detail.commands,
originX,
originY,
originW,
originH
}
}
}
}
};
boxOriginElem.detail = { ...boxViewElem.detail };
}
rotateElement(ctx, { x, y, w, h, angle }, () => {
drawBox(ctx, viewElem, {
originElem: elem,
drawBox(ctx, boxViewElem, {
originElem: boxOriginElem,
calcElemSize: { x, y, w, h, angle },
viewScaleInfo,
viewSizeInfo,
@ -41,8 +69,16 @@ export function drawPath(ctx: ViewContext2D, elem: Element<'path'>, opts: Render
ctx.scale((scaleNum * scaleW) / viewScaleInfo.scale, (scaleNum * scaleH) / viewScaleInfo.scale);
const pathStr = generateSVGPath(detail.commands || []);
const path2d = new Path2D(pathStr);
if (detail.fill) {
if (typeof detail.fill === 'string') {
ctx.fillStyle = detail.fill;
} else {
ctx.fillStyle = 'transparent';
}
}
if (detail.fill) {
ctx.fillStyle = detail.fill;
ctx.fill(path2d, fillRule as CanvasFillRule);
}

View file

@ -4,7 +4,7 @@ import { drawBox, drawBoxShadow } from './box';
export function drawRect(ctx: ViewContext2D, elem: Element<'rect'>, 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 }, () => {

View file

@ -140,19 +140,19 @@ export interface ElementGroupDetail extends ElementBaseDetail {
assets?: ElementAssets;
}
export interface ElementPathDetail extends ElementBaseDetail {
export type ElementPathDetail = ElementBaseDetail & {
// path: string;
commands: SVGPathCommand[];
originX: number;
originY: number;
originW: number;
originH: number;
fill?: string;
fill?: string | LinearGradientColor | RadialGradientColor;
stroke?: string;
strokeWidth?: number;
strokeLineCap?: 'butt' | 'round' | 'square';
fillRule?: string; // "evenodd" | "nonzero"
}
};
export interface ElementDetailMap {
rect: ElementRectDetail;

View file

@ -2,7 +2,7 @@ import type { CoreOptions } from './core';
export type IDrawMode = 'select' | 'drag' | 'readOnly';
export type IDrawFeature = 'ruler' | 'scroll' | 'scale' | 'info' | 'selectInGroup'; // TODO other feature
export type IDrawFeature = 'ruler' | 'scroll' | 'scale' | 'info' | 'selectInGroup' | 'snapToGrid'; // TODO other feature
export interface IDrawSettings {
mode?: IDrawMode;