import { Transform, ERenderItemEvent, RenderItem, MotaOffscreenCanvas2D, CanvasStyle } from '@motajs/render-core'; import { logger } from '@motajs/common'; import { clamp, isEqual, isNil } from 'lodash-es'; export type CircleParams = [ cx?: number, cy?: number, radius?: number, start?: number, end?: number ]; export type EllipseParams = [ cx?: number, cy?: number, radiusX?: number, radiusY?: number, start?: number, end?: number ]; export type LineParams = [x1: number, y1: number, x2: number, y2: number]; export type BezierParams = [ sx: number, sy: number, cp1x: number, cp1y: number, cp2x: number, cp2y: number, ex: number, ey: number ]; export type QuadParams = [ sx: number, sy: number, cpx: number, cpy: number, ex: number, ey: number ]; export type RectRCircleParams = [ r1: number, r2?: number, r3?: number, r4?: number ]; export type RectREllipseParams = [ rx1: number, ry1: number, rx2?: number, ry2?: number, rx3?: number, ry3?: number, rx4?: number, ry4?: number ]; export interface ILineProperty { /** 线宽 */ lineWidth: number; /** 线的虚线设置 */ lineDash?: number[]; /** 虚线偏移量 */ lineDashOffset?: number; /** 线的连接样式 */ lineJoin: CanvasLineJoin; /** 线的顶端样式 */ lineCap: CanvasLineCap; /** 线的斜接限制,当连接为miter类型时可填,默认为10 */ miterLimit: number; } export interface IGraphicProperty extends ILineProperty { /** 渲染模式,参考 {@link GraphicMode} */ mode: GraphicMode; /** 填充样式 */ fill: CanvasStyle; /** 描边样式 */ stroke: CanvasStyle; /** 填充算法 */ fillRule: CanvasFillRule; } export const enum GraphicMode { /** 仅填充 */ Fill, /** 仅描边 */ Stroke, /** 先填充,然后描边 */ FillAndStroke, /** 先描边,然后填充 */ StrokeAndFill } const enum GraphicModeProp { Fill, Stroke, StrokeAndFill } export interface EGraphicItemEvent extends ERenderItemEvent {} export abstract class GraphicItemBase extends RenderItem implements Required { mode: GraphicMode = GraphicMode.Fill; fill: CanvasStyle = '#ddd'; stroke: CanvasStyle = '#ddd'; lineWidth: number = 2; lineDash: number[] = []; lineDashOffset: number = 0; lineJoin: CanvasLineJoin = 'bevel'; lineCap: CanvasLineCap = 'butt'; miterLimit: number = 10; fillRule: CanvasFillRule = 'nonzero'; enableCache: boolean = false; private propFill: boolean = true; private propStroke: boolean = false; private strokeAndFill: boolean = false; private propFillSet: boolean = false; private actionStroke: boolean = false; private cachePath?: Path2D; protected pathDirty: boolean = true; /** * 获取这个元素的绘制路径 */ abstract getPath(): Path2D; protected render( canvas: MotaOffscreenCanvas2D, _transform: Transform ): void { const ctx = canvas.ctx; this.setCanvasState(canvas); if (this.pathDirty) { this.cachePath = this.getPath(); this.pathDirty = false; } const path = this.cachePath; if (!path) return; switch (this.mode) { case GraphicMode.Fill: ctx.fill(path, this.fillRule); break; case GraphicMode.Stroke: ctx.stroke(path); break; case GraphicMode.FillAndStroke: ctx.fill(path, this.fillRule); ctx.stroke(path); break; case GraphicMode.StrokeAndFill: ctx.stroke(path); ctx.fill(path, this.fillRule); break; } } protected isActionInElement(x: number, y: number): boolean { const ctx = this.cache.ctx; if (this.pathDirty) { this.cachePath = this.getPath(); this.pathDirty = false; } const path = this.cachePath; if (!path) return false; const fixX = x * devicePixelRatio; const fixY = y * devicePixelRatio; ctx.lineWidth = this.lineWidth; ctx.lineCap = this.lineCap; ctx.lineJoin = this.lineJoin; ctx.setLineDash(this.lineDash); if (this.actionStroke) { return ctx.isPointInStroke(path, fixX, fixY); } switch (this.mode) { case GraphicMode.Fill: return ctx.isPointInPath(path, fixX, fixY, this.fillRule); case GraphicMode.Stroke: case GraphicMode.FillAndStroke: case GraphicMode.StrokeAndFill: return ( ctx.isPointInPath(path, fixX, fixY, this.fillRule) || ctx.isPointInStroke(path, fixX, fixY) ); } } /** * 设置描边绘制的信息 * @param options 线的信息 */ setLineOption(options: Partial) { if (!isNil(options.lineWidth)) this.lineWidth = options.lineWidth; if (!isNil(options.lineDash)) this.lineDash = options.lineDash; if (!isNil(options.lineDashOffset)) this.lineDashOffset = options.lineDashOffset; if (!isNil(options.lineJoin)) this.lineJoin = options.lineJoin; if (!isNil(options.lineCap)) this.lineCap = options.lineCap; if (!isNil(options.miterLimit)) this.miterLimit = options.miterLimit; this.update(); } /** * 设置填充样式 * @param style 绘制样式 */ setFillStyle(style: CanvasStyle) { this.fill = style; this.update(); } /** * 设置描边样式 * @param style 绘制样式 */ setStrokeStyle(style: CanvasStyle) { this.stroke = style; this.update(); } /** * 设置填充原则 * @param rule 填充原则 */ setFillRule(rule: CanvasFillRule) { this.fillRule = rule; this.update(); } /** * 设置绘制模式,是描边还是填充 * @param mode 绘制模式 */ setMode(mode: GraphicMode) { this.mode = mode; this.update(); } /** * 检查渲染模式,参考 {@link GraphicPropsBase} 中的 fill stroke strokeAndFill 属性 */ private checkMode(mode: GraphicModeProp, value: boolean) { switch (mode) { case GraphicModeProp.Fill: this.propFill = value; this.propFillSet = true; break; case GraphicModeProp.Stroke: this.propStroke = value; break; case GraphicModeProp.StrokeAndFill: this.strokeAndFill = value; break; } if (this.strokeAndFill) { this.mode = GraphicMode.StrokeAndFill; } else { if (!this.propFillSet) { if (this.propStroke) { this.mode = GraphicMode.Stroke; } else { this.mode = GraphicMode.Fill; } } else { if (this.propFill && this.propStroke) { this.mode = GraphicMode.FillAndStroke; } else if (this.propFill) { this.mode = GraphicMode.Fill; } else if (this.propStroke) { this.mode = GraphicMode.Stroke; } else { this.mode = GraphicMode.Fill; } } } this.update(); } /** * 设置画布的渲染状态,在实际渲染前调用 * @param canvas 要设置的画布 */ protected setCanvasState(canvas: MotaOffscreenCanvas2D) { const ctx = canvas.ctx; ctx.fillStyle = this.fill; ctx.strokeStyle = this.stroke; ctx.lineWidth = this.lineWidth; ctx.setLineDash(this.lineDash); ctx.lineDashOffset = this.lineDashOffset; ctx.lineJoin = this.lineJoin; ctx.lineCap = this.lineCap; ctx.miterLimit = this.miterLimit; } protected handleProps( key: string, _prevValue: any, nextValue: any ): boolean { switch (key) { case 'fill': if (!this.assertType(nextValue, 'boolean', key)) return false; this.checkMode(GraphicModeProp.Fill, nextValue); return true; case 'stroke': if (!this.assertType(nextValue, 'boolean', key)) return false; this.checkMode(GraphicModeProp.Stroke, nextValue); return true; case 'strokeAndFill': if (!this.assertType(nextValue, 'boolean', key)) return false; this.checkMode(GraphicModeProp.StrokeAndFill, nextValue); return true; case 'fillRule': if (!this.assertType(nextValue, 'string', key)) return false; this.setFillRule(nextValue); return true; case 'fillStyle': this.setFillStyle(nextValue); return true; case 'strokeStyle': this.setStrokeStyle(nextValue); return true; case 'lineWidth': if (!this.assertType(nextValue, 'number', key)) return false; this.lineWidth = nextValue; this.update(); return true; case 'lineDash': if (!this.assertType(nextValue, Array, key)) return false; this.lineDash = nextValue as number[]; this.update(); return true; case 'lineDashOffset': if (!this.assertType(nextValue, 'number', key)) return false; this.lineDashOffset = nextValue; this.update(); return true; case 'lineJoin': if (!this.assertType(nextValue, 'string', key)) return false; this.lineJoin = nextValue; this.update(); return true; case 'lineCap': if (!this.assertType(nextValue, 'string', key)) return false; this.lineCap = nextValue; this.update(); return true; case 'miterLimit': if (!this.assertType(nextValue, 'number', key)) return false; this.miterLimit = nextValue; this.update(); return true; case 'actionStroke': if (!this.assertType(nextValue, 'boolean', key)) return false; this.actionStroke = nextValue; return true; } return false; } } export class Rect extends GraphicItemBase { pos(x: number, y: number): void { super.pos(x, y); this.pathDirty = true; } size(width: number, height: number): void { super.size(width, height); this.pathDirty = true; } getPath(): Path2D { const path = new Path2D(); path.rect(0, 0, this.width, this.height); return path; } } export class Circle extends GraphicItemBase { radius: number = 10; start: number = 0; end: number = Math.PI * 2; anchorX: number = 0.5; anchorY: number = 0.5; getPath(): Path2D { const path = new Path2D(); path.arc(this.radius, this.radius, this.radius, this.start, this.end); return path; } /** * 设置圆的半径 * @param radius 半径 */ setRadius(radius: number) { this.radius = radius; this.size(radius * 2, radius * 2); this.pathDirty = true; this.update(); } /** * 设置圆的起始与终止角度 * @param start 起始角度 * @param end 终止角度 */ setAngle(start: number, end: number) { this.start = start; this.end = end; this.pathDirty = true; this.update(); } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'radius': if (!this.assertType(nextValue, 'number', key)) return false; this.setRadius(nextValue); return true; case 'start': if (!this.assertType(nextValue, 'number', key)) return false; this.setAngle(nextValue, this.end); return true; case 'end': if (!this.assertType(nextValue, 'number', key)) return false; this.setAngle(this.start, nextValue); return true; case 'circle': { if (isEqual(nextValue, prevValue)) return true; const value = nextValue as CircleParams; if (!this.assertType(value, Array, key)) return false; const [cx, cy, radius, start, end] = value; if (!isNil(cx) && !isNil(cy)) { this.pos(cx, cy); } if (!isNil(radius)) { this.setRadius(radius); } if (!isNil(start) && !isNil(end)) { this.setAngle(start, end); } return true; } } return super.handleProps(key, prevValue, nextValue); } } export class Ellipse extends GraphicItemBase { radiusX: number = 10; radiusY: number = 10; start: number = 0; end: number = Math.PI * 2; anchorX: number = 0.5; anchorY: number = 0.5; getPath(): Path2D { const path = new Path2D(); path.ellipse( this.radiusX, this.radiusY, this.radiusX, this.radiusY, 0, this.start, this.end ); return path; } /** * 设置椭圆的横纵轴长度 * @param x 横轴长度 * @param y 纵轴长度 */ setRadius(x: number, y: number) { this.radiusX = x; this.radiusY = y; this.size(x, y); this.pathDirty = true; this.update(); } /** * 设置椭圆的起始与终止角度 * @param start 起始角度 * @param end 终止角度 */ setAngle(start: number, end: number) { this.start = start; this.end = end; this.pathDirty = true; this.update(); } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'radiusX': if (!this.assertType(nextValue, 'number', key)) return false; this.setRadius(nextValue, this.radiusY); return true; case 'radiusY': if (!this.assertType(nextValue, 'number', key)) return false; this.setRadius(this.radiusY, nextValue); return true; case 'start': if (!this.assertType(nextValue, 'number', key)) return false; this.setAngle(nextValue, this.end); return true; case 'end': if (!this.assertType(nextValue, 'number', key)) return false; this.setAngle(this.start, nextValue); return true; case 'ellipse': { if (isEqual(nextValue, prevValue)) return true; const value = nextValue as EllipseParams; if (!this.assertType(value, Array, key)) return false; const [cx, cy, radiusX, radiusY, start, end] = value; if (!isNil(cx) && !isNil(cy)) { this.pos(cx, cy); } if (!isNil(radiusX) && !isNil(radiusY)) { this.setRadius(radiusX, radiusY); } if (!isNil(start) && !isNil(end)) { this.setAngle(start, end); } return true; } } return super.handleProps(key, prevValue, nextValue); } } export class Line extends GraphicItemBase { x1: number = 0; y1: number = 0; x2: number = 0; y2: number = 0; mode: GraphicMode = GraphicMode.Stroke; getPath(): Path2D { const path = new Path2D(); const x = this.x; const y = this.y; path.moveTo(this.x1 - x, this.y1 - y); path.lineTo(this.x2 - x, this.y2 - y); return path; } /** * 设置第一个点的横纵坐标 */ setPoint1(x: number, y: number) { this.x1 = x; this.y1 = y; this.fitRect(); this.update(); } /** * 设置第二个点的横纵坐标 */ setPoint2(x: number, y: number) { this.x2 = x; this.y2 = y; this.fitRect(); this.update(); } private fitRect() { const left = Math.min(this.x1, this.x2); const top = Math.min(this.y1, this.y2); const right = Math.max(this.x1, this.x2); const bottom = Math.max(this.y1, this.y2); this.pos(left, top); this.size(right - left, bottom - top); this.pathDirty = true; } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'x1': if (!this.assertType(nextValue, 'number', key)) return false; this.setPoint1(nextValue, this.y1); return true; case 'y1': if (!this.assertType(nextValue, 'number', key)) return false; this.setPoint1(this.x1, nextValue); return true; case 'x2': if (!this.assertType(nextValue, 'number', key)) return false; this.setPoint2(nextValue, this.y2); return true; case 'y2': if (!this.assertType(nextValue, 'number', key)) return false; this.setPoint2(this.x2, nextValue); return true; case 'line': if (isEqual(nextValue, prevValue)) return true; if (!this.assertType(nextValue as number[], Array, key)) { return false; } this.setPoint1(nextValue[0], nextValue[1]); this.setPoint2(nextValue[2], nextValue[3]); return true; } return super.handleProps(key, prevValue, nextValue); } } export class BezierCurve extends GraphicItemBase { sx: number = 0; sy: number = 0; cp1x: number = 0; cp1y: number = 0; cp2x: number = 0; cp2y: number = 0; ex: number = 0; ey: number = 0; mode: GraphicMode = GraphicMode.Stroke; getPath(): Path2D { const path = new Path2D(); const x = this.x; const y = this.y; path.moveTo(this.sx - x, this.sy - y); path.bezierCurveTo( this.cp1x - x, this.cp1y - y, this.cp2x - x, this.cp2y - y, this.ex - x, this.ey - y ); return path; } /** * 设置起始点坐标 */ setStart(x: number, y: number) { this.sx = x; this.sy = y; this.fitRect(); this.update(); } /** * 设置控制点1坐标 */ setControl1(x: number, y: number) { this.cp1x = x; this.cp1y = y; this.fitRect(); this.update(); } /** * 设置控制点2坐标 */ setControl2(x: number, y: number) { this.cp2x = x; this.cp2y = y; this.fitRect(); this.update(); } /** * 设置终点坐标 */ setEnd(x: number, y: number) { this.ex = x; this.ey = y; this.fitRect(); this.update(); } protected isActionInElement(x: number, y: number): boolean { return x >= 0 && x < this.width && y >= 0 && y < this.height; } private fitRect() { const left = Math.min(this.sx, this.cp1x, this.cp2x, this.ex); const top = Math.min(this.sy, this.cp1y, this.cp2y, this.ey); const right = Math.max(this.sx, this.cp1x, this.cp2x, this.ex); const bottom = Math.max(this.sy, this.cp1y, this.cp2y, this.ey); this.pos(left, top); this.size(right - left, bottom - top); this.pathDirty = true; } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'sx': if (!this.assertType(nextValue, 'number', key)) return false; this.setStart(nextValue, this.sy); return true; case 'sy': if (!this.assertType(nextValue, 'number', key)) return false; this.setStart(this.sx, nextValue); return true; case 'cp1x': if (!this.assertType(nextValue, 'number', key)) return false; this.setControl1(nextValue, this.cp1y); return true; case 'cp1y': if (!this.assertType(nextValue, 'number', key)) return false; this.setControl1(this.cp1x, nextValue); return true; case 'cp2x': if (!this.assertType(nextValue, 'number', key)) return false; this.setControl2(nextValue, this.cp2y); return true; case 'cp2y': if (!this.assertType(nextValue, 'number', key)) return false; this.setControl2(this.cp2x, nextValue); return true; case 'ex': if (!this.assertType(nextValue, 'number', key)) return false; this.setEnd(nextValue, this.ey); return true; case 'ey': if (!this.assertType(nextValue, 'number', key)) return false; this.setEnd(this.ex, nextValue); return true; case 'curve': if (isEqual(nextValue, prevValue)) return true; if (!this.assertType(nextValue as number[], Array, key)) { return false; } this.setStart(nextValue[0], nextValue[1]); this.setControl1(nextValue[2], nextValue[3]); this.setControl2(nextValue[4], nextValue[5]); this.setEnd(nextValue[6], nextValue[7]); return true; } return super.handleProps(key, prevValue, nextValue); } } export class QuadraticCurve extends GraphicItemBase { sx: number = 0; sy: number = 0; cpx: number = 0; cpy: number = 0; ex: number = 0; ey: number = 0; mode: GraphicMode = GraphicMode.Stroke; getPath(): Path2D { const path = new Path2D(); const x = this.x; const y = this.y; path.moveTo(this.sx - x, this.sy - y); path.quadraticCurveTo( this.cpx - x, this.cpy - y, this.ex - x, this.ey - y ); return path; } protected render( canvas: MotaOffscreenCanvas2D, _transform: Transform ): void { const ctx = canvas.ctx; this.setCanvasState(canvas); ctx.beginPath(); ctx.moveTo(this.sx, this.sy); ctx.quadraticCurveTo(this.cpx, this.cpy, this.ex, this.ey); ctx.stroke(); } /** * 设置起始点坐标 */ setStart(x: number, y: number) { this.sx = x; this.sy = y; this.fitRect(); this.update(); } /** * 设置控制点坐标 */ setControl(x: number, y: number) { this.cpx = x; this.cpy = y; this.fitRect(); this.update(); } /** * 设置终点坐标 */ setEnd(x: number, y: number) { this.ex = x; this.ey = y; this.fitRect(); this.update(); } private fitRect() { const left = Math.min(this.sx, this.cpx, this.ex); const top = Math.min(this.sy, this.cpy, this.ey); const right = Math.max(this.sx, this.cpx, this.ex); const bottom = Math.max(this.sy, this.cpy, this.ey); this.pos(left, top); this.size(right - left, bottom - top); this.pathDirty = true; } protected isActionInElement(x: number, y: number): boolean { return x >= 0 && x < this.width && y >= 0 && y < this.height; } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'sx': if (!this.assertType(nextValue, 'number', key)) return false; this.setStart(nextValue, this.sy); return true; case 'sy': if (!this.assertType(nextValue, 'number', key)) return false; this.setStart(this.sx, nextValue); return true; case 'cpx': if (!this.assertType(nextValue, 'number', key)) return false; this.setControl(nextValue, this.cpy); return true; case 'cpy': if (!this.assertType(nextValue, 'number', key)) return false; this.setControl(this.cpx, nextValue); return true; case 'ex': if (!this.assertType(nextValue, 'number', key)) return false; this.setEnd(nextValue, this.ey); return true; case 'ey': if (!this.assertType(nextValue, 'number', key)) return false; this.setEnd(this.ex, nextValue); return true; case 'curve': if (isEqual(nextValue, prevValue)) return true; if (!this.assertType(nextValue as number[], Array, key)) { return false; } this.setStart(nextValue[0], nextValue[1]); this.setControl(nextValue[2], nextValue[3]); this.setEnd(nextValue[4], nextValue[5]); return true; } return super.handleProps(key, prevValue, nextValue); } } export class Path extends GraphicItemBase { /** 路径 */ path: Path2D = new Path2D(); /** * 获取当前路径 */ getPath() { return this.path; } /** * 为路径添加路径 * @param path 要添加的路径 */ addPath(path: Path2D) { this.path.addPath(path); this.pathDirty = true; this.update(); } protected isActionInElement(x: number, y: number): boolean { return x >= 0 && x < this.width && y >= 0 && y < this.height; } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'path': if (!this.assertType(nextValue, Path2D, key)) return false; this.path = nextValue; this.pathDirty = true; this.update(); return true; } return super.handleProps(key, prevValue, nextValue); } } export const enum RectRCorner { TopLeft, TopRight, BottomRight, BottomLeft } export class RectR extends GraphicItemBase { /** 圆角属性,四元素数组,每个元素是一个二元素数组,表示这个角的半径,顺序为 左上,右上,右下,左下 */ readonly corner: [radiusX: number, radiusY: number][] = [ [0, 0], [0, 0], [0, 0], [0, 0] ]; getPath(): Path2D { const path = new Path2D(); const { width: w, height: h } = this; const [[xtl, ytl], [xtr, ytr], [xbr, ybr], [xbl, ybl]] = this.corner; // 左上圆角终点 path.moveTo(xtl, 0); // 右上圆角起点 path.lineTo(w - xtr, 0); // 右上圆角终点 path.ellipse(w - xtr, ytr, xtr, ytr, 0, -Math.PI / 2, 0); // 右下圆角起点 path.lineTo(w, h - ybr); // 右下圆角终点 path.ellipse(w - xbr, h - ybr, xbr, ybr, 0, 0, Math.PI / 2); // 左下圆角起点 path.lineTo(xbl, h); // 左下圆角终点 path.ellipse(xbl, h - ybl, xbl, ybl, 0, Math.PI / 2, Math.PI); // 左上圆角起点 path.lineTo(0, ytl); // 左上圆角终点 path.ellipse(xtl, ytl, xtl, ytl, 0, Math.PI, -Math.PI / 2); path.closePath(); return path; } /** * 设置圆角半径 * @param x 横向半径 * @param y 纵向半径 */ setRadius(x: number, y: number, corner: RectRCorner) { const hw = this.width / 2; const hh = this.height / 2; this.corner[corner] = [clamp(x, 0, hw), clamp(y, 0, hh)]; this.pathDirty = true; this.update(); } /** * 设置圆形圆角参数 * @param circle 圆形圆角参数 */ setCircle(circle: RectRCircleParams) { const [r1, r2 = 0, r3 = 0, r4 = 0] = circle; switch (circle.length) { case 1: { this.setRadius(r1, r1, RectRCorner.BottomLeft); this.setRadius(r1, r1, RectRCorner.BottomRight); this.setRadius(r1, r1, RectRCorner.TopLeft); this.setRadius(r1, r1, RectRCorner.TopRight); break; } case 2: { this.setRadius(r1, r1, RectRCorner.TopLeft); this.setRadius(r1, r1, RectRCorner.BottomRight); this.setRadius(r2, r2, RectRCorner.BottomLeft); this.setRadius(r2, r2, RectRCorner.TopRight); break; } case 3: { this.setRadius(r1, r1, RectRCorner.TopLeft); this.setRadius(r2, r2, RectRCorner.TopRight); this.setRadius(r2, r2, RectRCorner.BottomLeft); this.setRadius(r3, r3, RectRCorner.BottomRight); break; } case 4: { this.setRadius(r1, r1, RectRCorner.TopLeft); this.setRadius(r2, r2, RectRCorner.TopRight); this.setRadius(r3, r3, RectRCorner.BottomRight); this.setRadius(r4, r4, RectRCorner.BottomLeft); break; } } } /** * 设置椭圆圆角参数 * @param ellipse 椭圆圆角参数 */ setEllipse(ellipse: RectREllipseParams) { const [rx1, ry1, rx2 = 0, ry2 = 0, rx3 = 0, ry3 = 0, rx4 = 0, ry4 = 0] = ellipse; switch (ellipse.length) { case 2: { this.setRadius(rx1, ry1, RectRCorner.BottomLeft); this.setRadius(rx1, ry1, RectRCorner.BottomRight); this.setRadius(rx1, ry1, RectRCorner.TopLeft); this.setRadius(rx1, ry1, RectRCorner.TopRight); break; } case 4: { this.setRadius(rx1, ry1, RectRCorner.TopLeft); this.setRadius(rx1, ry1, RectRCorner.BottomRight); this.setRadius(rx2, ry2, RectRCorner.BottomLeft); this.setRadius(rx2, ry2, RectRCorner.TopRight); break; } case 6: { this.setRadius(rx1, ry1, RectRCorner.TopLeft); this.setRadius(rx2, ry2, RectRCorner.TopRight); this.setRadius(rx2, ry2, RectRCorner.BottomLeft); this.setRadius(rx3, ry3, RectRCorner.BottomRight); break; } case 8: { this.setRadius(rx1, ry1, RectRCorner.TopLeft); this.setRadius(rx2, ry2, RectRCorner.TopRight); this.setRadius(rx3, ry3, RectRCorner.BottomRight); this.setRadius(rx4, ry4, RectRCorner.BottomLeft); break; } default: { logger.warn(58, ellipse.length.toString()); } } } protected handleProps( key: string, prevValue: any, nextValue: any ): boolean { switch (key) { case 'circle': { const value = nextValue as RectRCircleParams; if (!this.assertType(value, Array, key)) return false; this.setCircle(value); return true; } case 'ellipse': { const value = nextValue as RectREllipseParams; if (!this.assertType(value, Array, key)) return false; this.setEllipse(value); return true; } } return super.handleProps(key, prevValue, nextValue); } }