mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: add middleware ruler
This commit is contained in:
parent
6731799b26
commit
878f3a2737
15 changed files with 578 additions and 187 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
32
packages/core/src/middleware/ruler/index.ts
Normal file
32
packages/core/src/middleware/ruler/index.ts
Normal 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
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
231
packages/core/src/middleware/ruler/util.ts
Normal file
231
packages/core/src/middleware/ruler/util.ts
Normal 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
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
109
packages/idraw/src/idraw.ts
Normal 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
|
||||
// }
|
||||
}
|
||||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue