mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: improve renderer and middleware
This commit is contained in:
parent
6786f97db6
commit
ac60ce4bad
12 changed files with 114 additions and 83 deletions
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }, () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue