mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: improve renderer for elements in view
This commit is contained in:
parent
c7bef274c4
commit
2312251460
13 changed files with 120 additions and 112 deletions
|
|
@ -1,16 +1,4 @@
|
|||
import type {
|
||||
Data,
|
||||
PointSize,
|
||||
Point,
|
||||
Element,
|
||||
ElementType,
|
||||
ViewCalculator,
|
||||
ViewCalculatorOptions,
|
||||
ViewScaleInfo,
|
||||
ElementSize,
|
||||
ViewSizeInfo,
|
||||
ViewContext2D
|
||||
} from '@idraw/types';
|
||||
import type { Data, Point, Element, ElementType, ViewCalculator, ViewCalculatorOptions, ViewScaleInfo, ElementSize, ViewSizeInfo } from '@idraw/types';
|
||||
import { rotateElementVertexes } from '@idraw/util';
|
||||
|
||||
export class Calculator implements ViewCalculator {
|
||||
|
|
@ -20,13 +8,6 @@ export class Calculator implements ViewCalculator {
|
|||
this._opts = opts;
|
||||
}
|
||||
|
||||
private _getBoardSize(): { width: number; height: number } {
|
||||
return {
|
||||
width: this._opts.viewContent.boardContext.canvas.width,
|
||||
height: this._opts.viewContent.boardContext.canvas.height
|
||||
};
|
||||
}
|
||||
|
||||
viewScale(num: number, prevScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ViewScaleInfo {
|
||||
const scale = num;
|
||||
let offsetLeft = 0;
|
||||
|
|
@ -129,41 +110,22 @@ export class Calculator implements ViewCalculator {
|
|||
};
|
||||
}
|
||||
|
||||
isElementInView(elem: Element<ElementType>, scaleInfo: ViewScaleInfo): boolean {
|
||||
// TODO
|
||||
const { width, height } = this._getBoardSize();
|
||||
const { scale = 1, offsetTop = 0, offsetLeft = 0 } = scaleInfo;
|
||||
|
||||
const { angle = 0 } = elem;
|
||||
isElementInView(elem: ElementSize, scaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean {
|
||||
const { width, height } = viewSizeInfo;
|
||||
const { angle } = elem;
|
||||
const { x, y, w, h } = this.elementSize(elem, scaleInfo);
|
||||
const vertexes = rotateElementVertexes({ x, y, w, h, angle });
|
||||
|
||||
// Virtual View Point
|
||||
const vvpStart: PointSize = {
|
||||
x: Math.min(vertexes[0].x, vertexes[1].x, vertexes[2].x, vertexes[3].x),
|
||||
y: Math.min(vertexes[0].y, vertexes[1].y, vertexes[2].y, vertexes[3].y)
|
||||
};
|
||||
const vvpEnd: PointSize = {
|
||||
x: Math.max(vertexes[0].x, vertexes[1].x, vertexes[2].x, vertexes[3].x),
|
||||
y: Math.max(vertexes[0].y, vertexes[1].y, vertexes[2].y, vertexes[3].y)
|
||||
};
|
||||
|
||||
// Virtual Element Point
|
||||
const vep0: PointSize = { x: elem.x * scale, y: elem.y * scale };
|
||||
const vep1: PointSize = { x: (elem.x + elem.w) * scale, y: elem.y * scale };
|
||||
const vep2: PointSize = { x: (elem.x + elem.w) * scale, y: (elem.y + elem.h) * scale };
|
||||
const vep3: PointSize = { x: elem.x * scale, y: (elem.y + elem.h) * scale };
|
||||
|
||||
const isPointInRect = (p: PointSize) => {
|
||||
return p.x >= vvpStart.x && p.x <= vvpEnd.x && p.y >= vvpStart.y && p.y <= vvpEnd.y;
|
||||
};
|
||||
if (isPointInRect(vep0) || isPointInRect(vep1) || isPointInRect(vep2) || isPointInRect(vep3)) {
|
||||
return true;
|
||||
for (let i = 0; i < vertexes.length; i++) {
|
||||
const v = vertexes[i];
|
||||
if (v.x >= 0 && v.x <= width && v.y >= 0 && v.y <= height) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
isPointInElement(ctx: ViewContext2D, p: Point, elem: Element<ElementType>, scaleInfo: ViewScaleInfo): boolean {
|
||||
isPointInElement(p: Point, elem: Element<ElementType>, scaleInfo: ViewScaleInfo): boolean {
|
||||
const ctx = this._opts.viewContent.boardContext;
|
||||
const { angle = 0 } = elem;
|
||||
const { x, y, w, h } = this.elementSize(elem, scaleInfo);
|
||||
const vertexes = rotateElementVertexes({ x, y, w, h, angle });
|
||||
|
|
@ -181,14 +143,14 @@ export class Calculator implements ViewCalculator {
|
|||
return false;
|
||||
}
|
||||
|
||||
getPointElement(ctx: ViewContext2D, p: Point, data: Data, scaleInfo: ViewScaleInfo): { index: number; element: null | Element<ElementType> } {
|
||||
getPointElement(p: Point, data: Data, scaleInfo: ViewScaleInfo): { index: number; element: null | Element<ElementType> } {
|
||||
const result: { index: number; element: null | Element<ElementType> } = {
|
||||
index: -1,
|
||||
element: null
|
||||
};
|
||||
for (let i = 0; i < data.elements.length; i++) {
|
||||
const elem = data.elements[i];
|
||||
if (this.isPointInElement(ctx, p, elem, scaleInfo)) {
|
||||
if (this.isPointInElement(p, elem, scaleInfo)) {
|
||||
result.index = i;
|
||||
result.element = elem;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -34,13 +34,24 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
|
|||
|
||||
if (snapshot) {
|
||||
const { viewContext, helperContext, boardContext } = viewContent;
|
||||
|
||||
if (snapshot?.activeStore.data) {
|
||||
const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight, width, height, contextHeight, contextWidth, devicePixelRatio } = snapshot.activeStore;
|
||||
renderer.drawData(snapshot.activeStore.data, {
|
||||
scale: snapshot?.activeStore.scale,
|
||||
offsetTop: snapshot?.activeStore.offsetTop,
|
||||
offsetBottom: snapshot?.activeStore.offsetBottom,
|
||||
offsetLeft: snapshot?.activeStore.offsetLeft,
|
||||
offsetRight: snapshot?.activeStore.offsetRight
|
||||
scaleInfo: {
|
||||
scale,
|
||||
offsetTop,
|
||||
offsetBottom,
|
||||
offsetLeft,
|
||||
offsetRight
|
||||
},
|
||||
viewSize: {
|
||||
width,
|
||||
height,
|
||||
contextHeight,
|
||||
contextWidth,
|
||||
devicePixelRatio
|
||||
}
|
||||
});
|
||||
}
|
||||
beforeDrawFrame({ snapshot });
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export function drawPointWrapper(ctx: CanvasRenderingContext2D | ViewContext2D,
|
|||
|
||||
if (opts?.calculator) {
|
||||
const { calculator } = opts;
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts);
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts.scaleInfo);
|
||||
x = size.x;
|
||||
y = size.y;
|
||||
w = size.w;
|
||||
|
|
@ -41,7 +41,7 @@ export function drawHoverWrapper(ctx: CanvasRenderingContext2D | ViewContext2D,
|
|||
const { angle = 0 } = elem;
|
||||
if (opts?.calculator) {
|
||||
const { calculator } = opts;
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts);
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts.scaleInfo);
|
||||
x = size.x;
|
||||
y = size.y;
|
||||
w = size.w;
|
||||
|
|
@ -93,7 +93,7 @@ export function drawElementControllers(
|
|||
|
||||
if (opts?.calculator) {
|
||||
const { calculator } = opts;
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts);
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts.scaleInfo);
|
||||
x = size.x;
|
||||
y = size.y;
|
||||
w = size.w;
|
||||
|
|
@ -133,15 +133,13 @@ export function drawElementListShadows(
|
|||
const { angle = 0 } = elem;
|
||||
if (opts?.calculator) {
|
||||
const { calculator } = opts;
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts);
|
||||
const size = calculator.elementSize({ x, y, w, h }, opts.scaleInfo);
|
||||
x = size.x;
|
||||
y = size.y;
|
||||
w = size.w;
|
||||
h = size.h;
|
||||
}
|
||||
|
||||
const vertexes = rotateElementVertexes({ x, y, w, h, angle });
|
||||
|
||||
if (vertexes.length >= 2) {
|
||||
ctx.setLineDash([]);
|
||||
ctx.lineWidth = 1;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Point, PointWatcherEvent, BoardMiddleware, ElementSize } from '@idraw/types';
|
||||
import { drawPointWrapper, drawHoverWrapper, drawElementControllers, drawElementListShadows } from './draw-wrapper';
|
||||
import { drawPointWrapper, drawHoverWrapper, drawElementControllers } from './draw-wrapper';
|
||||
|
||||
export const MiddlewareSelector: BoardMiddleware = (opts) => {
|
||||
const { viewer, sharer, viewContent, calculator } = opts;
|
||||
|
|
@ -43,10 +43,9 @@ export const MiddlewareSelector: BoardMiddleware = (opts) => {
|
|||
if (sharer.getSharedStorage(keyActionType) === 'drag') {
|
||||
sharer.setSharedStorage(keyHoverElementSize, null);
|
||||
} else if (data) {
|
||||
const result = calculator.getPointElement(helperContext, e.point, data, getScaleInfo());
|
||||
const result = calculator.getPointElement(e.point, data, getScaleInfo());
|
||||
if (result.element) {
|
||||
const { x, y, w, h, angle } = result.element;
|
||||
// const { x, y, w, h, angle } = calculator.elementSize(result.element, getScaleInfo());
|
||||
sharer.setSharedStorage(keyHoverElementSize, { x, y, w, h, angle });
|
||||
viewer.drawFrame();
|
||||
return;
|
||||
|
|
@ -60,16 +59,23 @@ export const MiddlewareSelector: BoardMiddleware = (opts) => {
|
|||
pointStart: (e: PointWatcherEvent) => {
|
||||
const data = sharer.getActiveStorage('data');
|
||||
if (data) {
|
||||
const result = calculator.getPointElement(helperContext, e.point, data, getScaleInfo());
|
||||
const result = calculator.getPointElement(e.point, data, getScaleInfo());
|
||||
sharer.setActiveStorage('selectedIndexs', result.index >= 0 ? [result.index] : []);
|
||||
}
|
||||
const _actionType = sharer.getSharedStorage(keyActionType);
|
||||
const _hoverElementSize = sharer.getSharedStorage(keyHoverElementSize);
|
||||
if (getIndex() >= 0) {
|
||||
sharer.setSharedStorage(keyActionType, 'drag');
|
||||
prevPoint = e.point;
|
||||
} else if (sharer.getSharedStorage(keyActionType) !== null || sharer.getSharedStorage(keyHoverElementSize) !== null) {
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyHoverElementSize, null);
|
||||
viewer.drawFrame();
|
||||
} else if (_actionType !== null || _hoverElementSize !== null) {
|
||||
if (_actionType === 'click') {
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyHoverElementSize, null);
|
||||
} else {
|
||||
sharer.setSharedStorage(keyActionType, null);
|
||||
sharer.setSharedStorage(keyHoverElementSize, null);
|
||||
viewer.drawFrame();
|
||||
}
|
||||
}
|
||||
},
|
||||
pointMove: (e: PointWatcherEvent) => {
|
||||
|
|
@ -95,15 +101,25 @@ export const MiddlewareSelector: BoardMiddleware = (opts) => {
|
|||
},
|
||||
pointEnd(e: PointWatcherEvent) {
|
||||
sharer.setSharedStorage(keyActionType, 'click');
|
||||
viewer.drawFrame();
|
||||
const data = sharer.getActiveStorage('data');
|
||||
if (data) {
|
||||
const result = calculator.getPointElement(e.point, data, sharer.getActiveScaleInfo());
|
||||
if (result.element) {
|
||||
viewer.drawFrame();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
beforeDrawFrame({ snapshot }) {
|
||||
const { activeStore, sharedStore } = snapshot;
|
||||
const { data, selectedIndexs, scale, offsetLeft, offsetTop, offsetRight, offsetBottom } = activeStore;
|
||||
const { data, selectedIndexs, scale, offsetLeft, offsetTop, offsetRight, offsetBottom, width, height, contextHeight, contextWidth, devicePixelRatio } =
|
||||
activeStore;
|
||||
|
||||
const scaleInfo = { scale, offsetLeft, offsetTop, offsetRight, offsetBottom };
|
||||
const viewSize = { width, height, contextHeight, contextWidth, devicePixelRatio };
|
||||
|
||||
const hoverElement: ElementSize = sharedStore[keyHoverElementSize];
|
||||
const drawOpts = { calculator, scale, offsetLeft, offsetTop, offsetRight, offsetBottom };
|
||||
const drawOpts = { calculator, scaleInfo, viewSize };
|
||||
if (hoverElement) {
|
||||
drawHoverWrapper(helperContext, hoverElement, drawOpts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,22 @@ const data: Data = {
|
|||
]
|
||||
};
|
||||
|
||||
// const data: Data = {
|
||||
// elements: [
|
||||
// {
|
||||
// uuid: 'xxxx-0005',
|
||||
// x: 300,
|
||||
// y: 300,
|
||||
// w: 100,
|
||||
// h: 100,
|
||||
// type: 'circle',
|
||||
// desc: {
|
||||
// bgColor: '#009688'
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// };
|
||||
|
||||
export function getData() {
|
||||
return deepClone(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,20 +21,20 @@ const idraw = new iDraw(
|
|||
);
|
||||
idraw.setData(data);
|
||||
// idraw.scale(0.5);
|
||||
idraw.scale(1.5);
|
||||
idraw.scale(2);
|
||||
idraw.scrollX(-80);
|
||||
idraw.scrollY(-80);
|
||||
|
||||
const mount2 = document.querySelector('#mount') as HTMLDivElement;
|
||||
const data2 = getData();
|
||||
const idraw2 = new iDraw(
|
||||
mount2,
|
||||
Object.assign({}, opts, {
|
||||
// contextWidth: 500,
|
||||
// contextHeight: 400
|
||||
})
|
||||
);
|
||||
idraw2.setData(data2);
|
||||
// const mount2 = document.querySelector('#mount') as HTMLDivElement;
|
||||
// const data2 = getData();
|
||||
// const idraw2 = new iDraw(
|
||||
// mount2,
|
||||
// Object.assign({}, opts, {
|
||||
// // contextWidth: 500,
|
||||
// // contextHeight: 400
|
||||
// })
|
||||
// );
|
||||
// idraw2.setData(data2);
|
||||
|
||||
// const parseData = idraw.getData();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import { rotateElement } from '@idraw/util';
|
|||
export function drawCircle(ctx: ViewContext2D, elem: Element<'circle'>, opts: RendererDrawElementOptions) {
|
||||
const { desc, angle } = elem;
|
||||
const { bgColor = '#000000', borderColor = '#000000', borderWidth = 0 } = desc;
|
||||
const { calculator, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = opts;
|
||||
const { calculator, scaleInfo } = opts;
|
||||
const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = scaleInfo;
|
||||
const { x, y, w, h } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, { scale, offsetTop, offsetBottom, offsetLeft, offsetRight });
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
const a = w / 2;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export function drawElement(ctx: ViewContext2D, elem: Element<ElementType>, opts
|
|||
export function drawElementList(ctx: ViewContext2D, elements: Data['elements'], opts: RendererDrawElementOptions) {
|
||||
for (let i = elements.length - 1; i >= 0; i--) {
|
||||
const elem = elements[i];
|
||||
if (!opts.calculator.isElementInView(elem, opts)) {
|
||||
if (!opts.calculator.isElementInView(elem, opts.scaleInfo, opts.viewSize)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { rotateElement } from '@idraw/util';
|
|||
|
||||
export function drawImage(ctx: ViewContext2D, elem: Element<'image'>, opts: RendererDrawElementOptions) {
|
||||
const content = opts.loader.getContent(elem.uuid);
|
||||
const { calculator, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = opts;
|
||||
const { x, y, w, h, angle } = calculator.elementSize(elem, { scale, offsetTop, offsetBottom, offsetLeft, offsetRight });
|
||||
const { calculator, scaleInfo } = opts;
|
||||
const { x, y, w, h, angle } = calculator.elementSize(elem, scaleInfo);
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
if (!content) {
|
||||
opts.loader.load(elem as Element<'image'>);
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@ import { rotateElement } from '@idraw/util';
|
|||
|
||||
export function drawRect(ctx: ViewContext2D, elem: Element<'rect'>, opts: RendererDrawElementOptions) {
|
||||
// const { desc } = elem;
|
||||
const { calculator, scale, offsetTop, offsetBottom, offsetLeft, offsetRight } = opts;
|
||||
const { calculator, scaleInfo } = opts;
|
||||
|
||||
const { x, y, w, h, angle } = calculator.elementSize(
|
||||
{ x: elem.x, y: elem.y, w: elem.w, h: elem.h },
|
||||
{ scale, offsetTop, offsetBottom, offsetLeft, offsetRight }
|
||||
);
|
||||
const { x, y, w, h, angle } = calculator.elementSize({ x: elem.x, y: elem.y, w: elem.w, h: elem.h }, scaleInfo);
|
||||
rotateElement(ctx, { x, y, w, h, angle }, () => {
|
||||
let r: number = (elem.desc.borderRadius || 0) * scale;
|
||||
let r: number = (elem.desc.borderRadius || 0) * scaleInfo.scale;
|
||||
r = Math.min(r, w / 2, h / 2);
|
||||
if (w < r * 2 || h < r * 2) {
|
||||
r = 0;
|
||||
|
|
|
|||
|
|
@ -45,15 +45,24 @@ export class Renderer extends EventEmitter<RendererEventMap> implements BoardRen
|
|||
|
||||
scale(num: number) {
|
||||
const { sharer } = this._opts;
|
||||
const { data, offsetTop, offsetBottom, offsetLeft, offsetRight } = sharer.getActiveStoreSnapshot();
|
||||
// TODO calc offset data
|
||||
const { data, offsetTop, offsetBottom, offsetLeft, offsetRight, width, height, contextHeight, contextWidth, devicePixelRatio } =
|
||||
sharer.getActiveStoreSnapshot();
|
||||
if (data) {
|
||||
this.drawData(data, {
|
||||
scale: num,
|
||||
offsetTop,
|
||||
offsetBottom,
|
||||
offsetLeft,
|
||||
offsetRight
|
||||
scaleInfo: {
|
||||
scale: num,
|
||||
offsetTop,
|
||||
offsetBottom,
|
||||
offsetLeft,
|
||||
offsetRight
|
||||
},
|
||||
viewSize: {
|
||||
width,
|
||||
height,
|
||||
contextHeight,
|
||||
contextWidth,
|
||||
devicePixelRatio
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { ViewContent, ViewScaleInfo, ViewCalculator } from './view';
|
||||
import type { ViewContent, ViewScaleInfo, ViewCalculator, ViewSizeInfo } from './view';
|
||||
import type { Element } from './element';
|
||||
import type { LoaderEventMap, LoadElementType, LoadContent } from './loader';
|
||||
import type { UtilEventEmitter } from './util';
|
||||
|
|
@ -23,7 +23,10 @@ export interface RendererLoader extends UtilEventEmitter<LoaderEventMap> {
|
|||
getContent(uuid: string): LoadContent | null;
|
||||
}
|
||||
|
||||
export type RendererDrawOptions = ViewScaleInfo;
|
||||
export interface RendererDrawOptions {
|
||||
viewSize: ViewSizeInfo;
|
||||
scaleInfo: ViewScaleInfo;
|
||||
}
|
||||
|
||||
export interface RendererDrawElementOptions extends RendererDrawOptions {
|
||||
loader: RendererLoader;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Element, ElementType, ElementSize } from './element';
|
||||
import type { Point, PointSize } from './point';
|
||||
import type { Point } from './point';
|
||||
import type { Data } from './data';
|
||||
import type { ViewContext2D } from './context2d';
|
||||
|
||||
|
|
@ -31,16 +31,11 @@ export interface ViewCalculatorOptions {
|
|||
|
||||
export interface ViewCalculator {
|
||||
viewScale(num: number, prevScaleInfo: ViewScaleInfo, viewSize: ViewSizeInfo): ViewScaleInfo;
|
||||
isElementInView(elem: Element<ElementType>, scaleInfo: ViewScaleInfo): boolean;
|
||||
isPointInElement(ctx: ViewContext2D | CanvasRenderingContext2D | ViewContext2D, p: Point, elem: Element<ElementType>, scaleInfo: ViewScaleInfo): boolean;
|
||||
isElementInView(elem: Element<ElementType>, scaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): boolean;
|
||||
isPointInElement(p: Point, elem: Element<ElementType>, scaleInfo: ViewScaleInfo): boolean;
|
||||
elementSize(size: ElementSize, scaleInfo: ViewScaleInfo): ElementSize;
|
||||
viewScroll(opts: { moveX?: number; moveY?: number }, scaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeInfo): ViewScaleInfo;
|
||||
getPointElement(
|
||||
ctx: ViewContext2D | CanvasRenderingContext2D | ViewContext2D,
|
||||
p: Point,
|
||||
data: Data,
|
||||
scaleInfo: ViewScaleInfo
|
||||
): { index: number; element: null | Element<ElementType> };
|
||||
getPointElement(p: Point, data: Data, scaleInfo: ViewScaleInfo): { index: number; element: null | Element<ElementType> };
|
||||
// rotateElementSize(elemSize: ElementSize): PointSize[];
|
||||
// pointToViewPoint( p: Point): Point;
|
||||
// TODO
|
||||
|
|
|
|||
Loading…
Reference in a new issue