feat: add middleware ruler

This commit is contained in:
chenshenhai 2023-11-19 14:25:41 +08:00
parent 6731799b26
commit 878f3a2737
15 changed files with 578 additions and 187 deletions

View file

@ -342,7 +342,8 @@ export class Board<T extends BoardExtendEvent = BoardExtendEvent> {
clear() {
const { viewContent } = this._opts;
const { helperContext, viewContext, boardContext } = viewContent;
const { underContext, helperContext, viewContext, boardContext } = viewContent;
underContext.clearRect(0, 0, underContext.canvas.width, underContext.canvas.height);
helperContext.clearRect(0, 0, helperContext.canvas.width, helperContext.canvas.height);
viewContext.clearRect(0, 0, viewContext.canvas.width, viewContext.canvas.height);
boardContext.clearRect(0, 0, boardContext.canvas.width, boardContext.canvas.height);

View file

@ -44,7 +44,7 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
if (snapshot) {
const { scale, offsetTop, offsetBottom, offsetLeft, offsetRight, width, height, contextHeight, contextWidth, devicePixelRatio } = snapshot.activeStore;
const { viewContext, helperContext, boardContext } = viewContent;
const { underContext, viewContext, helperContext, boardContext } = viewContent;
if (snapshot?.activeStore.data) {
renderer.drawData(snapshot.activeStore.data, {
@ -66,8 +66,10 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
}
beforeDrawFrame({ snapshot });
boardContext.clearRect(0, 0, width, height);
boardContext.drawImage(underContext.canvas, 0, 0, width, height);
boardContext.drawImage(viewContext.canvas, 0, 0, width, height);
boardContext.drawImage(helperContext.canvas, 0, 0, width, height);
underContext.clearRect(0, 0, width, height);
viewContext.clearRect(0, 0, width, height);
helperContext.clearRect(0, 0, width, height);
afterDrawFrame({ snapshot });
@ -154,12 +156,15 @@ export class Viewer extends EventEmitter<BoardViewerEventMap> implements BoardVi
const newViewSize = { ...originViewSize, ...viewSize };
const { width, height, devicePixelRatio } = newViewSize;
const { boardContext, helperContext, viewContext } = this._opts.viewContent;
const { underContext, boardContext, helperContext, viewContext } = this._opts.viewContent;
boardContext.canvas.width = width * devicePixelRatio;
boardContext.canvas.height = height * devicePixelRatio;
boardContext.canvas.style.width = `${width}px`;
boardContext.canvas.style.height = `${height}px`;
underContext.canvas.width = width * devicePixelRatio;
underContext.canvas.height = height * devicePixelRatio;
helperContext.canvas.width = width * devicePixelRatio;
helperContext.canvas.height = height * devicePixelRatio;

View file

@ -7,6 +7,7 @@ import { Cursor } from './lib/cursor';
export { MiddlewareSelector } from './middleware/selector';
export { MiddlewareScroller } from './middleware/scroller';
export { MiddlewareScaler } from './middleware/scaler';
export { MiddlewareRuler } from './middleware/ruler';
export class Core {
private _board: Board<CoreEvent>;

View file

@ -0,0 +1,32 @@
import type { BoardMiddleware, CoreEvent } from '@idraw/types';
import { getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot } from '@idraw/util';
import { drawRulerBackground, drawXRuler, drawYRuler, calcXRulerScaleList, calcYRulerScaleList, drawUnderGrid } from './util';
export const MiddlewareRuler: BoardMiddleware<Record<string, any>, CoreEvent> = (opts) => {
const key = 'RULE';
const { viewContent } = opts;
const { helperContext, underContext } = viewContent;
return {
mode: key,
isDefault: true,
beforeDrawFrame: ({ snapshot }) => {
const viewScaleInfo = getViewScaleInfoFromSnapshot(snapshot);
const viewSizeInfo = getViewSizeInfoFromSnapshot(snapshot);
drawRulerBackground(helperContext, { viewScaleInfo, viewSizeInfo });
const xList = calcXRulerScaleList({ viewScaleInfo, viewSizeInfo });
drawXRuler(helperContext, { scaleList: xList });
const yList = calcYRulerScaleList({ viewScaleInfo, viewSizeInfo });
drawYRuler(helperContext, { scaleList: yList });
drawUnderGrid(underContext, {
xList,
yList,
viewScaleInfo,
viewSizeInfo
});
}
};
};

View file

@ -0,0 +1,231 @@
import type { ViewScaleInfo, ViewSizeInfo, ViewContext2D } from '@idraw/types';
import { Context2D, formatNumber, rotateByCenter } from '@idraw/util';
const rulerSize = 16;
const background = '#FFFFFFA8';
const borderColor = '#00000080';
const scaleColor = '#000000';
const textColor = '#00000080';
const fontFamily = 'monospace';
const fontSize = 10;
const fontWeight = 100;
const gridColor = '#AAAAAA30';
const gridKeyColor = '#AAAAAA70';
// const rulerUnit = 10;
// const rulerKeyUnit = 100;
// const rulerSubKeyUnit = 50;
interface RulerScale {
num: number;
showNum: boolean;
position: number;
isKeyNum: boolean;
isSubKeyNum: boolean;
}
function calcRulerScaleList(opts: { scale: number; viewLength: number; viewOffset: number }): RulerScale[] {
const { scale, viewLength, viewOffset } = opts;
const list: RulerScale[] = [];
let rulerUnit = 10;
rulerUnit = formatNumber(rulerUnit / scale, { decimalPlaces: 0 });
rulerUnit = Math.max(10, Math.min(rulerUnit, 1000));
const rulerKeyUnit = rulerUnit * 10;
const rulerSubKeyUnit = rulerUnit * 5;
let index: number = 0;
const viewUnit = rulerUnit * scale;
const startNum = 0 - viewOffset;
const startPosition = 0;
const remainderNum = startNum % viewUnit;
const firstNum = (startNum - remainderNum + viewUnit) / scale;
const firstPosition = startPosition + (viewUnit - remainderNum);
while (firstPosition + index * viewUnit < viewLength) {
const num = firstNum + index * rulerUnit;
const position = firstPosition + index * viewUnit;
const rulerScale = {
num: formatNumber(num, { decimalPlaces: 0 }),
position,
showNum: num % rulerKeyUnit === 0,
isKeyNum: num % rulerKeyUnit === 0,
isSubKeyNum: num % rulerSubKeyUnit === 0
};
// if (viewUnit >= rulerSubKeyUnit) {
// rulerScale.isKeyNum = true;
// }
list.push(rulerScale);
index++;
}
return list;
}
export function calcXRulerScaleList(opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): RulerScale[] {
const { viewScaleInfo, viewSizeInfo } = opts;
const { scale, offsetLeft } = viewScaleInfo;
const { width } = viewSizeInfo;
return calcRulerScaleList({
scale,
viewLength: width,
viewOffset: offsetLeft
});
}
export function calcYRulerScaleList(opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo }): RulerScale[] {
const { viewScaleInfo, viewSizeInfo } = opts;
const { scale, offsetTop } = viewScaleInfo;
const { height } = viewSizeInfo;
return calcRulerScaleList({
scale,
viewLength: height,
viewOffset: offsetTop
});
}
export function drawXRuler(
ctx: ViewContext2D,
opts: {
scaleList: RulerScale[];
}
) {
const { scaleList } = opts;
const scaleDrawStart = rulerSize;
const scaleDrawEnd = (rulerSize * 4) / 5;
const subKeyScaleDrawEnd = (rulerSize * 2) / 5;
const keyScaleDrawEnd = (rulerSize * 1) / 5;
const fontStart = rulerSize / 5;
for (let i = 0; i < scaleList.length; i++) {
const item = scaleList[i];
if (item.position < rulerSize) {
continue;
}
ctx.beginPath();
ctx.moveTo(item.position, scaleDrawStart);
ctx.lineTo(item.position, item.isKeyNum ? keyScaleDrawEnd : item.isSubKeyNum ? subKeyScaleDrawEnd : scaleDrawEnd);
ctx.closePath();
ctx.fillStyle = scaleColor;
ctx.stroke();
if (item.isKeyNum) {
ctx.fillStyle = textColor;
ctx.textBaseline = 'top';
ctx.$setFont({
fontWeight,
fontSize,
fontFamily
});
ctx.fillText(`${item.num}`, item.position + fontStart, fontStart);
}
}
}
export function drawYRuler(
ctx: ViewContext2D,
opts: {
scaleList: RulerScale[];
}
) {
const { scaleList } = opts;
const scaleDrawStart = rulerSize;
const scaleDrawEnd = (rulerSize * 4) / 5;
const subKeyScaleDrawEnd = (rulerSize * 2) / 5;
const keyScaleDrawEnd = (rulerSize * 1) / 5;
const fontStart = rulerSize / 5;
for (let i = 0; i < scaleList.length; i++) {
const item = scaleList[i];
if (item.position < rulerSize) {
continue;
}
ctx.beginPath();
ctx.moveTo(scaleDrawStart, item.position);
ctx.lineTo(item.isKeyNum ? keyScaleDrawEnd : item.isSubKeyNum ? subKeyScaleDrawEnd : scaleDrawEnd, item.position);
ctx.closePath();
ctx.fillStyle = scaleColor;
ctx.stroke();
if (item.showNum === true) {
const textX = fontStart;
const textY = item.position + fontStart;
const numText = `${item.num}`;
rotateByCenter(ctx, -90, { x: textX, y: textY }, () => {
ctx.fillStyle = textColor;
ctx.textBaseline = 'top';
ctx.$setFont({
fontWeight,
fontSize,
fontFamily
});
ctx.fillText(numText, fontStart + fontSize, item.position + fontStart);
});
}
}
}
export function drawRulerBackground(
ctx: ViewContext2D,
opts: {
viewScaleInfo: ViewScaleInfo;
viewSizeInfo: ViewSizeInfo;
}
) {
const { viewSizeInfo } = opts;
const { width, height } = viewSizeInfo;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(width + 1, 0);
ctx.lineTo(width + 1, rulerSize);
ctx.lineTo(rulerSize, rulerSize);
ctx.lineTo(rulerSize, height + 1);
ctx.lineTo(0, height + 1);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fillStyle = background;
ctx.fill();
ctx.strokeStyle = borderColor;
ctx.stroke();
}
export function drawUnderGrid(
ctx: ViewContext2D,
opts: {
xList: RulerScale[];
yList: RulerScale[];
viewScaleInfo: ViewScaleInfo;
viewSizeInfo: ViewSizeInfo;
}
) {
const { xList, yList, viewSizeInfo } = opts;
const { width, height } = viewSizeInfo;
for (let i = 0; i < xList.length; i++) {
const item = xList[i];
ctx.beginPath();
ctx.moveTo(item.position, 0);
ctx.lineTo(item.position, height);
if (item.isKeyNum === true || item.isSubKeyNum === true) {
ctx.strokeStyle = gridKeyColor;
} else {
ctx.strokeStyle = gridColor;
}
ctx.lineWidth = 1;
ctx.closePath();
ctx.stroke();
}
for (let i = 0; i < yList.length; i++) {
const item = yList[i];
ctx.beginPath();
ctx.moveTo(0, item.position);
ctx.lineTo(width, item.position);
if (item.isKeyNum === true || item.isSubKeyNum === true) {
ctx.strokeStyle = gridKeyColor;
} else {
ctx.strokeStyle = gridColor;
}
ctx.lineWidth = 1;
ctx.closePath();
ctx.stroke();
}
// TODO
}

View file

@ -1,4 +1,3 @@
import type { ElementSize } from '@idraw/types';
import type { Point, BoardMiddleware, PointWatcherEvent, BoardWatherWheelXEvent, BoardWatherWheelYEvent } from '@idraw/types';
import { drawScroller, isPointInScrollThumb } from './util';
// import type { ScrollbarThumbType } from './util';

View file

@ -1,18 +1,18 @@
import type { Point, PointSize, BoardViewerFrameSnapshot, ViewScaleInfo, ViewSizeInfo, ViewContext2D, ElementSize } from '@idraw/types';
import type { Point, BoardViewerFrameSnapshot, ViewScaleInfo, ViewSizeInfo, ViewContext2D, ElementSize } from '@idraw/types';
import { getViewScaleInfoFromSnapshot, getViewSizeInfoFromSnapshot } from '@idraw/util';
import { keyActivePoint, keyActiveThumbType, keyPrevPoint, keyXThumbRect, keyYThumbRect } from './config';
const minScrollerWidth = 12;
const scrollerLineWidth = 16;
const scrollerAlpha = 0.12;
const scrollerThumbAlpha = 0.36;
export type ScrollbarThumbType = 'X' | 'Y';
const scrollConfig = {
width: minScrollerWidth,
color: '#000000',
showBackground: true
thumbColor: '#000000AA',
scrollBarColor: '#FFFFFF60',
showScrollBar: false
};
function isPointAtRect(helperContext: ViewContext2D, p: Point, rect: ElementSize): boolean {
@ -81,7 +81,8 @@ function calcScrollerInfo(viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeIn
ySize = height;
}
const xStart = lineSize / 2;
// const xStart = lineSize / 2;
const xStart = lineSize;
const xEnd = width - xSize - lineSize;
let translateX = xStart;
@ -90,11 +91,12 @@ function calcScrollerInfo(viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeIn
} else if (offsetRight > 0) {
translateX = xEnd;
} else if (offsetLeft <= 0 && xSize > 0 && !(offsetLeft === 0 && offsetRight === 0)) {
translateX = xSize / 2 + ((width - xSize) * Math.abs(offsetLeft)) / (Math.abs(offsetLeft) + Math.abs(offsetRight));
translateX = Math.min(Math.max(0, translateX - xSize / 2), width - xSize);
translateX = xStart + ((width - xSize) * Math.abs(offsetLeft)) / (Math.abs(offsetLeft) + Math.abs(offsetRight));
translateX = Math.min(Math.max(0, translateX - xStart), width - xSize);
}
const yStart = lineSize / 2;
// const yStart = lineSize / 2;
const yStart = lineSize;
const yEnd = height - ySize - lineSize;
let translateY = yStart;
if (offsetTop > 0) {
@ -102,8 +104,8 @@ function calcScrollerInfo(viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeIn
} else if (offsetBottom > 0) {
translateY = yEnd;
} else if (offsetTop <= 0 && ySize > 0 && !(offsetTop === 0 && offsetBottom === 0)) {
translateY = ySize / 2 + ((height - ySize) * Math.abs(offsetTop)) / (Math.abs(offsetTop) + Math.abs(offsetBottom));
translateY = Math.min(Math.max(0, translateY - ySize / 2), height - ySize);
translateY = yStart + ((height - ySize) * Math.abs(offsetTop)) / (Math.abs(offsetTop) + Math.abs(offsetBottom));
translateY = Math.min(Math.max(0, translateY - yStart), height - ySize);
}
const xThumbRect: ElementSize = {
x: translateX,
@ -123,7 +125,8 @@ function calcScrollerInfo(viewScaleInfo: ViewScaleInfo, viewSizeInfo: ViewSizeIn
ySize,
translateY,
translateX,
color: '#0000007A',
thumbColor: scrollConfig.thumbColor,
scrollBarColor: scrollConfig.scrollBarColor,
xThumbRect,
yThumbRect
};
@ -143,43 +146,52 @@ function drawScrollerThumb(
}
): void {
let { x, y, h, w } = opts;
const { color, axis } = opts;
if (axis === 'X') {
y = y + h / 4 + 0;
h = h / 2;
} else if (axis === 'Y') {
x = x + w / 4 + 0;
w = w / 2;
}
let r = opts.r;
r = Math.min(r, w / 2, h / 2);
if (w < r * 2 || h < r * 2) {
r = 0;
}
ctx.globalAlpha = scrollerThumbAlpha;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.save();
ctx.shadowColor = '#FFFFFF';
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 1;
{
const { color, axis } = opts;
if (axis === 'X') {
y = y + h / 4 + 0;
h = h / 2;
} else if (axis === 'Y') {
x = x + w / 4 + 0;
w = w / 2;
}
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.setLineDash([]);
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.stroke();
let r = opts.r;
r = Math.min(r, w / 2, h / 2);
if (w < r * 2 || h < r * 2) {
r = 0;
}
ctx.globalAlpha = scrollerThumbAlpha;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.setLineDash([]);
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.stroke();
}
ctx.restore();
}
function drawScrollerInfo(helperContext: ViewContext2D, opts: { viewScaleInfo: ViewScaleInfo; viewSizeInfo: ViewSizeInfo; scrollInfo: ScrollInfo }) {
@ -202,9 +214,8 @@ function drawScrollerInfo(helperContext: ViewContext2D, opts: { viewScaleInfo: V
}
// x-bar
if (scrollConfig.showBackground === true) {
ctx.globalAlpha = scrollerAlpha;
ctx.fillStyle = wrapper.color;
if (scrollConfig.showScrollBar === true) {
ctx.fillStyle = wrapper.scrollBarColor;
// x-line
ctx.fillRect(0, height - wrapper.lineSize, width, wrapper.lineSize);
}
@ -215,13 +226,12 @@ function drawScrollerInfo(helperContext: ViewContext2D, opts: { viewScaleInfo: V
axis: 'X',
...xThumbRect,
r: wrapper.lineSize / 2,
color: wrapper.color
color: wrapper.thumbColor
});
// y-bar
if (scrollConfig.showBackground === true) {
ctx.globalAlpha = scrollerAlpha;
ctx.fillStyle = wrapper.color;
if (scrollConfig.showScrollBar === true) {
ctx.fillStyle = wrapper.scrollBarColor;
// y-line
ctx.fillRect(width - wrapper.lineSize, 0, wrapper.lineSize, height);
}
@ -232,7 +242,7 @@ function drawScrollerInfo(helperContext: ViewContext2D, opts: { viewScaleInfo: V
axis: 'Y',
...yThumbRect,
r: wrapper.lineSize / 2,
color: wrapper.color
color: wrapper.thumbColor
});
ctx.globalAlpha = 1;

View file

@ -4,14 +4,16 @@
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style>
html, body {
margin: 0; padding: 0; background: #1e1e1e;
margin: 0;
padding: 0;
/* background: #1e1e1e; */
}
#mount {
margin-top: 20px;
margin-left: 20px;
}
#mount canvas {
border-right: 1px solid #aaaaaa40;
/* border-right: 1px solid #aaaaaa40;
border-bottom: 1px solid #aaaaaa40;
background-image:
linear-gradient(#aaaaaa40 1px, transparent 0),
@ -20,7 +22,7 @@
linear-gradient(90deg, #aaa 1px, transparent 0);
background-size: 10px 10px, 10px 10px, 50px 50px, 50px 50px;
background-color: #ffffff;
margin: 0 20px;
margin: 0 20px; */
}
</style>
</head>

109
packages/idraw/src/idraw.ts Normal file
View file

@ -0,0 +1,109 @@
import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler, MiddlewareRuler } from '@idraw/core';
import type { PointSize, IDrawOptions, Data, ViewSizeInfo, IDrawEvent } from '@idraw/types';
export class iDraw {
private _core: Core;
private _opts: IDrawOptions;
constructor(mount: HTMLDivElement, opts: IDrawOptions) {
const core = new Core(mount, opts);
this._core = core;
this._opts = opts;
core.use(MiddlewareScroller);
core.use(MiddlewareSelector);
core.use(MiddlewareScaler);
core.use(MiddlewareRuler);
}
setData(data: Data) {
this._core.setData(data);
}
getData(): Data | null {
return this._core.getData();
}
selectElement() {
// TODO
}
selectElementByIndex() {
// TODO
}
cancelElement() {
// TODO
}
cancelElementByIndex() {
// TODO
}
updateElement() {
// TODO
}
addElement() {
// TODO
}
deleteElement() {
// TODO
}
moveUpElement() {
// TODO
}
moveDownElement() {
// TODO
}
insertElementBefore() {
// TODO
}
insertElementBeforeIndex() {
// TODO
}
insertElementAfter() {
// TODO
}
insertElementAfterIndex() {
// TODO
}
scale(opts: { scale: number; point: PointSize }) {
this._core.scale(opts);
}
resize(opts: Partial<ViewSizeInfo>) {
this._core.resize(opts);
}
on<T extends keyof IDrawEvent>(name: T, callback: (e: IDrawEvent[T]) => void) {
this._core.on(name, callback);
}
off<T extends keyof IDrawEvent>(name: T, callback: (e: IDrawEvent[T]) => void) {
this._core.off(name, callback);
}
trigger<T extends keyof IDrawEvent>(name: T, e: IDrawEvent[T]) {
this._core.trigger(name, e);
}
// scrollLeft() {
// // TODO
// }
// scrollTop() {
// // TODO
// }
// exportDataURL() {
// // TODO
// }
}

View file

@ -1,108 +1,94 @@
import { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler } from '@idraw/core';
import type { PointSize, IDrawOptions, Data, ViewSizeInfo, IDrawEvent } from '@idraw/types';
export class iDraw {
private _core: Core;
private _opts: IDrawOptions;
constructor(mount: HTMLDivElement, opts: IDrawOptions) {
const core = new Core(mount, opts);
this._core = core;
this._opts = opts;
core.use(MiddlewareScroller);
core.use(MiddlewareSelector);
core.use(MiddlewareScaler);
}
setData(data: Data) {
this._core.setData(data);
}
getData(): Data | null {
return this._core.getData();
}
selectElement() {
// TODO
}
selectElementByIndex() {
// TODO
}
cancelElement() {
// TODO
}
cancelElementByIndex() {
// TODO
}
updateElement() {
// TODO
}
addElement() {
// TODO
}
deleteElement() {
// TODO
}
moveUpElement() {
// TODO
}
moveDownElement() {
// TODO
}
insertElementBefore() {
// TODO
}
insertElementBeforeIndex() {
// TODO
}
insertElementAfter() {
// TODO
}
insertElementAfterIndex() {
// TODO
}
scale(opts: { scale: number; point: PointSize }) {
this._core.scale(opts);
}
resize(opts: Partial<ViewSizeInfo>) {
this._core.resize(opts);
}
on<T extends keyof IDrawEvent>(name: T, callback: (e: IDrawEvent[T]) => void) {
this._core.on(name, callback);
}
off<T extends keyof IDrawEvent>(name: T, callback: (e: IDrawEvent[T]) => void) {
this._core.off(name, callback);
}
trigger<T extends keyof IDrawEvent>(name: T, e: IDrawEvent[T]) {
this._core.trigger(name, e);
}
// scrollLeft() {
// // TODO
// }
// scrollTop() {
// // TODO
// }
// exportDataURL() {
// // TODO
// }
}
export { iDraw } from './idraw';
export type * from '@idraw/types';
export { Core, MiddlewareSelector, MiddlewareScroller, MiddlewareScaler } from '@idraw/core';
export { Renderer } from '@idraw/renderer';
export {
delay,
compose,
throttle,
downloadImageFromCanvas,
parseFileToBase64,
pickFile,
parseFileToText,
toColorHexStr,
toColorHexNum,
isColorStr,
colorNameToHex,
colorToCSS,
colorToLinearGradientCSS,
mergeHexColorAlpha,
createUUID,
isAssetId,
createAssetId,
deepClone,
sortDataAsserts,
istype,
loadImage,
loadSVG,
loadHTML,
is,
check,
createBoardContexts,
createContext2D,
createOffscreenContext2D,
EventEmitter,
calcDistance,
calcSpeed,
equalPoint,
equalTouchPoint,
vaildPoint,
vaildTouchPoint,
getCenterFromTwoPoints,
Store,
getViewScaleInfoFromSnapshot,
getViewSizeInfoFromSnapshot,
Context2D,
rotateElement,
parseRadianToAngle,
parseAngleToRadian,
rotateElementVertexes,
getElementRotateVertexes,
calcElementCenter,
calcElementCenterFromVertexes,
rotatePointInGroup,
limitAngle,
getSelectedElementUUIDs,
validateElements,
calcElementsContextSize,
calcElementsViewInfo,
getElemenetsAssetIds,
findElementFromList,
findElementsFromList,
updateElementInList,
getGroupQueueFromList,
getElementSize,
mergeElementAsset,
filterElementAsset,
isResourceElement,
checkRectIntersect,
viewScale,
viewScroll,
calcViewElementSize,
calcViewPointSize,
calcViewVertexes,
isViewPointInElement,
getViewPointAtElement,
isElementInView,
rotatePoint,
rotateVertexes,
getElementVertexes,
calcElementVertexesInGroup,
calcElementVertexesQueueInGroup,
calcElementQueueVertexesQueueInGroup,
calcElementSizeController,
generateSVGPath,
parseSVGPath,
generateHTML,
parseHTML,
compressImage,
formatNumber,
matrixToAngle,
matrixToRadian,
getDefaultElementDetailConfig,
calcViewBoxSize
} from '@idraw/util';

View file

@ -23,9 +23,10 @@ export interface ViewSizeInfo extends ViewContextSize {
}
export interface ViewContent {
boardContext: ViewContext2D;
viewContext: ViewContext2D;
helperContext: ViewContext2D;
boardContext: ViewContext2D;
underContext: ViewContext2D;
}
export interface ViewCalculatorOptions {

View file

@ -50,7 +50,7 @@ export {
getViewPointAtElement,
isElementInView
} from './lib/view-calc';
export { rotatePoint, rotateVertexes } from './lib/rotate';
export { rotatePoint, rotateVertexes, rotateByCenter } from './lib/rotate';
export { getElementVertexes, calcElementVertexesInGroup, calcElementVertexesQueueInGroup, calcElementQueueVertexesQueueInGroup } from './lib/vertex';
export { calcElementSizeController } from './lib/controller';
export { generateSVGPath, parseSVGPath } from './lib/svg-path';

View file

@ -32,8 +32,10 @@ export function createBoardContexts(ctx: CanvasRenderingContext2D, opts?: { devi
};
const viewContext = createContext2D(ctxOpts);
const helperContext = createContext2D(ctxOpts);
const underContext = createContext2D(ctxOpts);
const boardContext = createContext2D({ ctx, ...ctxOpts });
const content: ViewContent = {
underContext,
viewContext,
helperContext,
boardContext

View file

@ -1,4 +1,7 @@
export function formatNumber(num: number, opts?: { decimalPlaces?: number }): number {
const decimalPlaces = opts?.decimalPlaces || 2;
let decimalPlaces = 2;
if (typeof opts?.decimalPlaces !== 'undefined' && opts?.decimalPlaces >= 0) {
decimalPlaces = opts.decimalPlaces;
}
return parseFloat(num.toFixed(decimalPlaces));
}

View file

@ -1,4 +1,4 @@
import type { ViewContext2D, PointSize, ElementSize, ViewRectVertexes, Element, ElementType } from '@idraw/types';
import type { ViewContext2D, PointSize, ElementSize, ViewRectVertexes, Element } from '@idraw/types';
import { calcDistance } from './point';
// import { calcElementVertexes } from './vertex';
@ -18,26 +18,35 @@ export function parseAngleToRadian(angle: number): number {
// return p;
// }
export function rotateByCenter(
ctx: ViewContext2D | CanvasRenderingContext2D | ViewContext2D,
angle: number,
center: PointSize,
callback: (ctx: ViewContext2D | CanvasRenderingContext2D) => void
): void {
const radian = parseAngleToRadian(angle || 0);
if (center && (radian > 0 || radian < 0)) {
ctx.translate(center.x, center.y);
ctx.rotate(radian);
ctx.translate(-center.x, -center.y);
}
callback(ctx);
if (center && (radian > 0 || radian < 0)) {
ctx.translate(center.x, center.y);
ctx.rotate(-radian);
ctx.translate(-center.x, -center.y);
}
}
export function rotateElement(
ctx: ViewContext2D | CanvasRenderingContext2D | ViewContext2D,
elemSize: ElementSize,
callback: (ctx: ViewContext2D | CanvasRenderingContext2D) => void
): void {
const center = calcElementCenter(elemSize);
const radian = parseAngleToRadian(elemSize.angle || 0);
if (center && (radian > 0 || radian < 0)) {
ctx.translate(center.x, center.y);
ctx.rotate(radian);
ctx.translate(-center.x, -center.y);
}
callback(ctx);
if (center && (radian > 0 || radian < 0)) {
ctx.translate(center.x, center.y);
ctx.rotate(-radian);
ctx.translate(-center.x, -center.y);
}
rotateByCenter(ctx, elemSize.angle || 0, center, () => {
callback(ctx);
});
}
export function calcElementCenter(elem: ElementSize): PointSize {