Merged PR 200: Implement the Canvas API rather than using SVG
This commit is contained in:
parent
af1b32c8d6
commit
04d79688cb
6 changed files with 712 additions and 11 deletions
146
src/Components/Canvas/Canvas.tsx
Normal file
146
src/Components/Canvas/Canvas.tsx
Normal file
|
@ -0,0 +1,146 @@
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import { IPoint } from '../../Interfaces/IPoint';
|
||||
import { BAR_WIDTH } from '../Bar/Bar';
|
||||
|
||||
interface ICanvasProps {
|
||||
width: number
|
||||
height: number
|
||||
draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
|
||||
className?: string
|
||||
style?: React.CSSProperties
|
||||
};
|
||||
|
||||
function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject<HTMLCanvasElement> {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const frameCount = useRef(0);
|
||||
const translatePos = useRef({
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
|
||||
const startDragOffset = useRef({
|
||||
x: 0,
|
||||
y: 0
|
||||
});
|
||||
|
||||
const scale = useRef(1.0);
|
||||
const scaleMultiplier = useRef(0.8);
|
||||
const dragging = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
let animationFrameId = 0;
|
||||
if (canvas === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
// add event listeners to handle screen drag
|
||||
function MouseDown(evt: MouseEvent): void {
|
||||
dragging.current = true;
|
||||
startDragOffset.current.x = evt.clientX - translatePos.current.x;
|
||||
startDragOffset.current.y = evt.clientY - translatePos.current.y;
|
||||
}
|
||||
|
||||
function MouseUp(evt: MouseEvent): void {
|
||||
dragging.current = false;
|
||||
}
|
||||
|
||||
function MouseMove(evt: MouseEvent): void {
|
||||
if (!dragging.current || context === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
translatePos.current.x = evt.clientX - startDragOffset.current.x;
|
||||
translatePos.current.y = evt.clientY - startDragOffset.current.y;
|
||||
frameCount.current++;
|
||||
draw(context, frameCount.current, scale.current, translatePos.current);
|
||||
}
|
||||
function HandleScroll(evt: WheelEvent): void {
|
||||
if (context === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (evt.deltaY >= 0) {
|
||||
scale.current *= scaleMultiplier.current;
|
||||
} else {
|
||||
scale.current /= scaleMultiplier.current;
|
||||
}
|
||||
|
||||
draw(context, frameCount.current, scale.current, translatePos.current);
|
||||
evt.preventDefault();
|
||||
}
|
||||
canvas.addEventListener('mousedown', MouseDown);
|
||||
window.addEventListener('mouseup', MouseUp);
|
||||
canvas.addEventListener('mousemove', MouseMove);
|
||||
canvas.addEventListener('wheel', HandleScroll);
|
||||
|
||||
function Render(): void {
|
||||
if (context === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
frameCount.current++;
|
||||
draw(context, frameCount.current, scale.current, translatePos.current);
|
||||
animationFrameId = window.requestAnimationFrame(Render);
|
||||
}
|
||||
|
||||
Render();
|
||||
|
||||
return () => {
|
||||
window.cancelAnimationFrame(animationFrameId);
|
||||
canvas.removeEventListener('mousedown', MouseDown);
|
||||
window.removeEventListener('mouseup', MouseUp);
|
||||
canvas.removeEventListener('mousemove', MouseMove);
|
||||
canvas.removeEventListener('wheel', HandleScroll);
|
||||
};
|
||||
}, [draw]);
|
||||
|
||||
return canvasRef;
|
||||
}
|
||||
|
||||
function UseSVGAutoResizer(
|
||||
setViewer: React.Dispatch<React.SetStateAction<Viewer>>
|
||||
): void {
|
||||
React.useEffect(() => {
|
||||
function OnResize(): void {
|
||||
setViewer({
|
||||
viewerWidth: window.innerWidth - BAR_WIDTH,
|
||||
viewerHeight: window.innerHeight
|
||||
});
|
||||
}
|
||||
window.addEventListener('resize', OnResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', OnResize);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
interface Viewer {
|
||||
viewerWidth: number
|
||||
viewerHeight: number
|
||||
}
|
||||
|
||||
export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element {
|
||||
const canvasRef = UseCanvas(draw);
|
||||
|
||||
const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
|
||||
viewerWidth: width,
|
||||
viewerHeight: height
|
||||
});
|
||||
|
||||
UseSVGAutoResizer(setViewer);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
style={style}
|
||||
width={viewerWidth}
|
||||
height={viewerHeight}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
82
src/Components/Canvas/Dimension.ts
Normal file
82
src/Components/Canvas/Dimension.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { NOTCHES_LENGTH } from '../../utils/default';
|
||||
|
||||
interface IDimensionProps {
|
||||
id: string
|
||||
xStart: number
|
||||
yStart: number
|
||||
xEnd: number
|
||||
yEnd: number
|
||||
text: string
|
||||
strokeWidth: number
|
||||
scale?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 2D Parametric function. Returns a new coordinate from the origin coordinate
|
||||
* See for more details https://en.wikipedia.org/wiki/Parametric_equation.
|
||||
* TL;DR a parametric function is a function with a parameter
|
||||
* @param x0 Origin coordinate
|
||||
* @param t The parameter
|
||||
* @param vx Transform vector
|
||||
* @returns Returns a new coordinate from the origin coordinate
|
||||
*/
|
||||
function ApplyParametric(x0: number, t: number, vx: number): number {
|
||||
return x0 + t * vx;
|
||||
}
|
||||
|
||||
export function RenderDimension(ctx: CanvasRenderingContext2D, props: IDimensionProps): void {
|
||||
const scale = props.scale ?? 1;
|
||||
const strokeStyle = 'black';
|
||||
const lineWidth = 2 / scale;
|
||||
|
||||
/// We need to find the points of the notches
|
||||
// Get the vector of the line
|
||||
const [deltaX, deltaY] = [(props.xEnd - props.xStart), (props.yEnd - props.yStart)];
|
||||
const rotation = (Math.atan2(deltaY, deltaX) / (2 * Math.PI));
|
||||
|
||||
// Get the unit vector
|
||||
const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
const [unitX, unitY] = [deltaX / norm, deltaY / norm];
|
||||
|
||||
// Get the perpandicular vector
|
||||
const [perpVecX, perpVecY] = [unitY, -unitX];
|
||||
|
||||
// Use the parametric function to get the coordinates (x = x0 + t * v.x)
|
||||
const notchesLength = NOTCHES_LENGTH / scale;
|
||||
const startTopX = ApplyParametric(props.xStart, notchesLength, perpVecX);
|
||||
const startTopY = ApplyParametric(props.yStart, notchesLength, perpVecY);
|
||||
const startBottomX = ApplyParametric(props.xStart, -notchesLength, perpVecX);
|
||||
const startBottomY = ApplyParametric(props.yStart, -notchesLength, perpVecY);
|
||||
|
||||
const endTopX = ApplyParametric(props.xEnd, notchesLength, perpVecX);
|
||||
const endTopY = ApplyParametric(props.yEnd, notchesLength, perpVecY);
|
||||
const endBottomX = ApplyParametric(props.xEnd, -notchesLength, perpVecX);
|
||||
const endBottomY = ApplyParametric(props.yEnd, -notchesLength, perpVecY);
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.strokeStyle = strokeStyle;
|
||||
ctx.fillStyle = strokeStyle;
|
||||
ctx.moveTo(startTopX, startTopY);
|
||||
ctx.lineTo(startBottomX, startBottomY);
|
||||
ctx.stroke();
|
||||
ctx.moveTo(props.xStart, props.yStart);
|
||||
ctx.lineTo(props.xEnd, props.yEnd);
|
||||
ctx.stroke();
|
||||
ctx.moveTo(endTopX, endTopY);
|
||||
ctx.lineTo(endBottomX, endBottomY);
|
||||
ctx.stroke();
|
||||
const textX = (props.xStart + props.xEnd) / 2;
|
||||
const textY = (props.yStart + props.yEnd) / 2;
|
||||
ctx.font = `${16 / scale}px Verdana`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.translate(textX, textY);
|
||||
ctx.rotate(rotation * Math.PI * 2);
|
||||
ctx.fillText(props.text, 0, 0);
|
||||
|
||||
ctx.restore();
|
||||
}
|
345
src/Components/Canvas/DimensionLayer.ts
Normal file
345
src/Components/Canvas/DimensionLayer.ts
Normal file
|
@ -0,0 +1,345 @@
|
|||
import { Orientation } from '../../Enums/Orientation';
|
||||
import { Position } from '../../Enums/Position';
|
||||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { SHOW_SELF_DIMENSIONS, SHOW_BORROWER_DIMENSIONS, SHOW_CHILDREN_DIMENSIONS } from '../../utils/default';
|
||||
import { MakeRecursionDFSIterator, Pairwise } from '../../utils/itertools';
|
||||
import { TransformX, TransformY } from '../../utils/svg';
|
||||
import { RenderDimension } from './Dimension';
|
||||
|
||||
const MODULE_STROKE_WIDTH = 1;
|
||||
|
||||
export function AddDimensions(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
container: IContainerModel,
|
||||
dimMapped: number[],
|
||||
currentTransform: [number, number],
|
||||
scale: number,
|
||||
depth: number
|
||||
): void {
|
||||
ctx.beginPath();
|
||||
if (SHOW_SELF_DIMENSIONS && container.properties.showSelfDimensions.length > 0) {
|
||||
ActionByPosition(
|
||||
ctx,
|
||||
dimMapped,
|
||||
container.properties.showSelfDimensions,
|
||||
AddHorizontalSelfDimension,
|
||||
AddVerticalSelfDimension,
|
||||
[container,
|
||||
currentTransform,
|
||||
scale]
|
||||
);
|
||||
}
|
||||
|
||||
if (SHOW_BORROWER_DIMENSIONS && container.properties.showDimensionWithMarks.length > 0) {
|
||||
ActionByPosition(
|
||||
ctx,
|
||||
dimMapped,
|
||||
container.properties.showDimensionWithMarks,
|
||||
AddHorizontalBorrowerDimension,
|
||||
AddVerticalBorrowerDimension,
|
||||
[container,
|
||||
depth,
|
||||
currentTransform,
|
||||
scale]
|
||||
);
|
||||
}
|
||||
|
||||
if (SHOW_CHILDREN_DIMENSIONS && container.properties.showChildrenDimensions.length > 0 && container.children.length >= 2) {
|
||||
ActionByPosition(
|
||||
ctx,
|
||||
dimMapped,
|
||||
container.properties.showChildrenDimensions,
|
||||
AddHorizontalChildrenDimension,
|
||||
AddVerticalChildrenDimension,
|
||||
[container,
|
||||
currentTransform,
|
||||
scale]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction that call another function given the positions
|
||||
* @param dimMapped Position mapped depending on the Position enum in order:
|
||||
* [0:left, 1:bottom, 2:up, 3:right]
|
||||
* @param positions List of positions
|
||||
* @param horizontalAction Action called when a left or right position is present
|
||||
* @param verticalAction Action called when a down or up position is present
|
||||
* @param params Params for the actions
|
||||
* (the two actions must have the same number of params, and in the same order)
|
||||
*/
|
||||
function ActionByPosition(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
dimMapped: number[],
|
||||
positions: Position[],
|
||||
horizontalAction: (ctx: CanvasRenderingContext2D, dim: number, ...params: any[]) => void,
|
||||
verticalAction: (ctx: CanvasRenderingContext2D, dim: number, ...params: any[]) => void,
|
||||
params: any[]
|
||||
): void {
|
||||
positions.forEach((position: Position) => {
|
||||
const dim = dimMapped[position];
|
||||
switch (position) {
|
||||
case Position.Left:
|
||||
case Position.Right:
|
||||
verticalAction(ctx, dim, ...params);
|
||||
break;
|
||||
case Position.Down:
|
||||
case Position.Up:
|
||||
horizontalAction(ctx, dim, ...params);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function AddHorizontalChildrenDimension(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
yDim: number,
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const childrenId = `dim-y${yDim.toFixed(0)}-children-${container.properties.id}`;
|
||||
|
||||
const lastChild = container.children[container.children.length - 1];
|
||||
let xChildrenStart = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
let xChildrenEnd = TransformX(lastChild.properties.x, lastChild.properties.width, lastChild.properties.positionReference);
|
||||
|
||||
// Find the min and max
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
const child = container.children[i];
|
||||
const left = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
|
||||
if (left < xChildrenStart) {
|
||||
xChildrenStart = left;
|
||||
}
|
||||
const right = TransformX(child.properties.x, child.properties.width, child.properties.positionReference);
|
||||
if (right > xChildrenEnd) {
|
||||
xChildrenEnd = right;
|
||||
}
|
||||
}
|
||||
|
||||
if (xChildrenStart === xChildrenEnd) {
|
||||
// do not show an empty dimension
|
||||
return;
|
||||
}
|
||||
|
||||
const textChildren = (xChildrenEnd - xChildrenStart)
|
||||
.toFixed(2)
|
||||
.toString();
|
||||
|
||||
const offset = currentTransform[0] + container.properties.x;
|
||||
RenderDimension(ctx, {
|
||||
id: childrenId,
|
||||
xStart: xChildrenStart + offset,
|
||||
xEnd: xChildrenEnd + offset,
|
||||
yStart: yDim,
|
||||
yEnd: yDim,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: textChildren,
|
||||
scale
|
||||
});
|
||||
}
|
||||
|
||||
function AddVerticalChildrenDimension(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
xDim: number,
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const childrenId = `dim-x${xDim.toFixed(0)}-children-${container.properties.id}`;
|
||||
|
||||
const lastChild = container.children[container.children.length - 1];
|
||||
let yChildrenStart = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
||||
let yChildrenEnd = TransformY(lastChild.properties.y, lastChild.properties.height, lastChild.properties.positionReference);
|
||||
|
||||
// Find the min and max
|
||||
for (let i = container.children.length - 2; i >= 0; i--) {
|
||||
const child = container.children[i];
|
||||
const top = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
|
||||
if (top < yChildrenStart) {
|
||||
yChildrenStart = top;
|
||||
}
|
||||
const bottom = TransformY(child.properties.y, child.properties.height, child.properties.positionReference);
|
||||
if (bottom > yChildrenEnd) {
|
||||
yChildrenEnd = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if (yChildrenStart === yChildrenEnd) {
|
||||
// do not show an empty dimension
|
||||
return;
|
||||
}
|
||||
|
||||
const textChildren = (yChildrenEnd - yChildrenStart)
|
||||
.toFixed(2)
|
||||
.toString();
|
||||
|
||||
const offset = currentTransform[0] + container.properties.x;
|
||||
|
||||
RenderDimension(ctx, {
|
||||
id: childrenId,
|
||||
xStart: xDim,
|
||||
xEnd: xDim,
|
||||
yStart: yChildrenStart + offset,
|
||||
yEnd: yChildrenEnd + offset,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: textChildren,
|
||||
scale
|
||||
});
|
||||
}
|
||||
|
||||
function AddHorizontalBorrowerDimension(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
yDim: number,
|
||||
container: IContainerModel,
|
||||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
container: childContainer, currentTransform: childCurrentTransform
|
||||
} of it) {
|
||||
const isHidden = !childContainer.properties.markPosition.includes(Orientation.Horizontal);
|
||||
if (isHidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const x = TransformX(
|
||||
childContainer.properties.x,
|
||||
childContainer.properties.width,
|
||||
childContainer.properties.positionReference
|
||||
);
|
||||
|
||||
const restoredX = x + childCurrentTransform[0];
|
||||
|
||||
marks.push(
|
||||
restoredX
|
||||
);
|
||||
}
|
||||
|
||||
const restoredX = container.properties.x + currentTransform[0];
|
||||
marks.push(restoredX);
|
||||
marks.push(restoredX + container.properties.width);
|
||||
marks.sort((a, b) => a - b);
|
||||
let count = 0;
|
||||
for (const { cur, next } of Pairwise(marks)) {
|
||||
const id = `dim-y${yDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`;
|
||||
RenderDimension(ctx, {
|
||||
id,
|
||||
xStart: cur,
|
||||
xEnd: next,
|
||||
yStart: yDim,
|
||||
yEnd: yDim,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: (next - cur).toFixed(0).toString(),
|
||||
scale
|
||||
});
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
function AddVerticalBorrowerDimension(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
xDim: number,
|
||||
container: IContainerModel,
|
||||
depth: number,
|
||||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const it = MakeRecursionDFSIterator(container, depth, currentTransform);
|
||||
const marks = []; // list of vertical lines for the dimension
|
||||
for (const {
|
||||
container: childContainer, currentTransform: childCurrentTransform
|
||||
} of it) {
|
||||
const isHidden = !childContainer.properties.markPosition.includes(Orientation.Vertical);
|
||||
if (isHidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const y = TransformY(
|
||||
childContainer.properties.y,
|
||||
childContainer.properties.height,
|
||||
childContainer.properties.positionReference
|
||||
);
|
||||
|
||||
const restoredy = y + childCurrentTransform[1];
|
||||
|
||||
marks.push(
|
||||
restoredy
|
||||
);
|
||||
}
|
||||
|
||||
const restoredY = container.properties.y + currentTransform[1];
|
||||
marks.push(restoredY);
|
||||
marks.push(restoredY + container.properties.height);
|
||||
marks.sort((a, b) => a - b);
|
||||
let count = 0;
|
||||
for (const { cur, next } of Pairwise(marks)) {
|
||||
const id = `dim-x${xDim.toFixed(0)}-borrow-${container.properties.id}-{${count}}`;
|
||||
RenderDimension(ctx, {
|
||||
id,
|
||||
xStart: xDim,
|
||||
xEnd: xDim,
|
||||
yStart: cur,
|
||||
yEnd: next,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: (next - cur).toFixed(0).toString(),
|
||||
scale
|
||||
});
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
function AddVerticalSelfDimension(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
xDim: number,
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const height = container.properties.height;
|
||||
const idVert = `dim-x${xDim.toFixed(0)}-${container.properties.id}`;
|
||||
const yStart = container.properties.y + currentTransform[1];
|
||||
const yEnd = yStart + height;
|
||||
const textVert = height
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
RenderDimension(ctx, {
|
||||
id: idVert,
|
||||
xStart: xDim,
|
||||
yStart,
|
||||
xEnd: xDim,
|
||||
yEnd,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text: textVert,
|
||||
scale
|
||||
});
|
||||
}
|
||||
|
||||
function AddHorizontalSelfDimension(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
yDim: number,
|
||||
container: IContainerModel,
|
||||
currentTransform: [number, number],
|
||||
scale: number
|
||||
): void {
|
||||
const width = container.properties.width;
|
||||
const id = `dim-y${yDim.toFixed(0)}-${container.properties.id}`;
|
||||
const xStart = container.properties.x + currentTransform[0];
|
||||
const xEnd = xStart + width;
|
||||
const text = width
|
||||
.toFixed(0)
|
||||
.toString();
|
||||
RenderDimension(ctx, {
|
||||
id,
|
||||
xStart,
|
||||
yStart: yDim,
|
||||
xEnd,
|
||||
yEnd: yDim,
|
||||
strokeWidth: MODULE_STROKE_WIDTH,
|
||||
text,
|
||||
scale
|
||||
});
|
||||
}
|
57
src/Components/Canvas/Selector.ts
Normal file
57
src/Components/Canvas/Selector.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { IContainerModel } from '../../Interfaces/IContainerModel';
|
||||
import { SHOW_SELECTOR_TEXT } from '../../utils/default';
|
||||
import { GetAbsolutePosition } from '../../utils/itertools';
|
||||
import { RemoveMargin } from '../../utils/svg';
|
||||
|
||||
interface ISelectorProps {
|
||||
selected?: IContainerModel
|
||||
scale?: number
|
||||
}
|
||||
|
||||
export function RenderSelector(ctx: CanvasRenderingContext2D, frameCount: number, props: ISelectorProps): void {
|
||||
if (props.selected === undefined || props.selected === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scale = (props.scale ?? 1);
|
||||
let [x, y] = GetAbsolutePosition(props.selected);
|
||||
let [width, height] = [
|
||||
props.selected.properties.width,
|
||||
props.selected.properties.height
|
||||
];
|
||||
|
||||
({ x, y, width, height } = RemoveMargin(x, y, width, height,
|
||||
props.selected.properties.margin.left,
|
||||
props.selected.properties.margin.bottom,
|
||||
props.selected.properties.margin.top,
|
||||
props.selected.properties.margin.right
|
||||
));
|
||||
|
||||
const xText = x + width / 2;
|
||||
const yText = y + height / 2;
|
||||
|
||||
const style: React.CSSProperties = {
|
||||
stroke: '#3B82F6',
|
||||
strokeWidth: 4 / scale,
|
||||
fillOpacity: 0,
|
||||
transitionProperty: 'all',
|
||||
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||
transitionDuration: '150ms',
|
||||
animation: 'fadein 750ms ease-in alternate infinite'
|
||||
};
|
||||
|
||||
ctx.strokeStyle = '#3B82F6';
|
||||
ctx.lineWidth = 4 / scale;
|
||||
ctx.globalAlpha = 0.25 * (Math.sin(frameCount * 0.0125) ** 2);
|
||||
ctx.strokeRect(x, y, width, height);
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = 'black';
|
||||
|
||||
if (SHOW_SELECTOR_TEXT) {
|
||||
ctx.font = `${16 / scale}px Verdana`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(props.selected.properties.displayedText, xText, yText);
|
||||
ctx.textAlign = 'left';
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue