feat: improve renderer for elements in view

This commit is contained in:
chenshenhai 2023-04-22 18:13:32 +08:00
parent c7bef274c4
commit 2312251460
13 changed files with 120 additions and 112 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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