From f7fbefd27f7f6972cfb5a45a68e5d0aed9593ae4 Mon Sep 17 00:00:00 2001 From: Gustav Neustadt <35671734+gustavneustadt@users.noreply.github.com> Date: Sat, 11 Feb 2023 16:59:32 +0100 Subject: [PATCH] feat(2d): add smooth corners and sharpness to rect (#310) --- packages/2d/src/components/Rect.ts | 21 +++++++++-- packages/2d/src/utils/CanvasUtils.ts | 52 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/packages/2d/src/components/Rect.ts b/packages/2d/src/components/Rect.ts index d097f1b0..f1425791 100644 --- a/packages/2d/src/components/Rect.ts +++ b/packages/2d/src/components/Rect.ts @@ -5,17 +5,28 @@ import { } from '@motion-canvas/core/lib/types'; import {Shape, ShapeProps} from './Shape'; import {drawRoundRect} from '../utils'; +import {initial, signal} from '../decorators'; import {spacingSignal} from '../decorators/spacingSignal'; -import {SignalValue} from '@motion-canvas/core/lib/signals'; +import {SignalValue, SimpleSignal} from '@motion-canvas/core/lib/signals'; export interface RectProps extends ShapeProps { radius?: SignalValue; + smoothCorners?: SignalValue; + cornerSharpness?: SignalValue; } export class Rect extends Shape { @spacingSignal('radius') public declare readonly radius: SpacingSignal; + @initial(false) + @signal() + public declare readonly smoothCorners: SimpleSignal; + + @initial(0.6) + @signal() + public declare readonly cornerSharpness: SimpleSignal; + public constructor(props: RectProps) { super(props); } @@ -23,8 +34,10 @@ export class Rect extends Shape { protected override getPath(): Path2D { const path = new Path2D(); const radius = this.radius(); + const smoothCorners = this.smoothCorners(); + const cornerSharpness = this.cornerSharpness(); const rect = RectType.fromSizeCentered(this.size()); - drawRoundRect(path, rect, radius); + drawRoundRect(path, rect, radius, smoothCorners, cornerSharpness); return path; } @@ -37,8 +50,10 @@ export class Rect extends Shape { const path = new Path2D(); const rippleSize = this.rippleSize(); const radius = this.radius().addScalar(rippleSize); + const smoothCorners = this.smoothCorners(); + const cornerSharpness = this.cornerSharpness(); const rect = RectType.fromSizeCentered(this.size()).expand(rippleSize); - drawRoundRect(path, rect, radius); + drawRoundRect(path, rect, radius, smoothCorners, cornerSharpness); return path; } diff --git a/packages/2d/src/utils/CanvasUtils.ts b/packages/2d/src/utils/CanvasUtils.ts index 2064bfc1..2f994856 100644 --- a/packages/2d/src/utils/CanvasUtils.ts +++ b/packages/2d/src/utils/CanvasUtils.ts @@ -39,6 +39,8 @@ export function drawRoundRect( context: CanvasRenderingContext2D | Path2D, rect: Rect, radius: Spacing, + smoothCorners: boolean, + cornerSharpness: number, ) { if ( radius.top === 0 && @@ -70,6 +72,56 @@ export function drawRoundRect( rect, ); + if (smoothCorners) { + const sharpness = (radius: number): number => { + const val = radius * cornerSharpness; + return radius - val; + }; + + context.moveTo(rect.left + topLeft, rect.top); + context.lineTo(rect.right - topRight, rect.top); + + context.bezierCurveTo( + rect.right - sharpness(topRight), + rect.top, + rect.right, + rect.top + sharpness(topRight), + rect.right, + rect.top + topRight, + ); + context.lineTo(rect.right, rect.bottom - bottomRight); + + context.bezierCurveTo( + rect.right, + rect.bottom - sharpness(bottomRight), + rect.right - sharpness(bottomRight), + rect.bottom, + rect.right - bottomRight, + rect.bottom, + ); + context.lineTo(rect.left + bottomLeft, rect.bottom); + + context.bezierCurveTo( + rect.left + sharpness(bottomLeft), + rect.bottom, + rect.left, + rect.bottom - sharpness(bottomLeft), + rect.left, + rect.bottom - bottomLeft, + ); + context.lineTo(rect.left, rect.top + topLeft); + + context.bezierCurveTo( + rect.left, + rect.top + sharpness(topLeft), + rect.left + sharpness(topLeft), + rect.top, + rect.left + topLeft, + rect.top, + ); + return; + } + context.moveTo(rect.left + topLeft, rect.top); context.arcTo(rect.right, rect.top, rect.right, rect.bottom, topRight); context.arcTo(rect.right, rect.bottom, rect.left, rect.bottom, bottomRight);