From 33012d80a3b311e4b8b0ec1b59f8ead248f0fe28 Mon Sep 17 00:00:00 2001 From: Ehsan Samavati Date: Mon, 24 Jun 2024 21:03:15 +0300 Subject: [PATCH] refactor(layout): add Layout class and enums for canvas fit and alignment The Layout class provides methods to manage the fit and alignment of a Rive animation within an HTML canvas. It includes the following features: - Fit enum with options: Cover, Contain, Fill, FitWidth, FitHeight, None, ScaleDown - Alignment enum with options: Center, TopLeft, TopCenter, TopRight, CenterLeft, CenterRight, BottomLeft, BottomCenter, BottomRight - Constructor to set layout parameters (fit, alignment, min/max X/Y coordinates) - Methods to get runtime Fit and Alignment values for the Rive canvas - Helper method to create a new Layout instance with modified parameters This commit also exports the Layout class and enums from the rive.ts entry point. --- js/src/layout/Layout.ts | 148 +++++++++++++++++++ js/src/layout/enums/Alignment.enum.ts | 41 +++++ js/src/layout/enums/Fit.enum.ts | 33 +++++ js/src/layout/enums/index.ts | 2 + js/src/layout/index.ts | 3 + js/src/layout/types/LayoutParameters.type.ts | 31 ++++ js/src/layout/types/index.ts | 1 + js/src/rive.ts | 146 +----------------- 8 files changed, 263 insertions(+), 142 deletions(-) create mode 100644 js/src/layout/Layout.ts create mode 100644 js/src/layout/enums/Alignment.enum.ts create mode 100644 js/src/layout/enums/Fit.enum.ts create mode 100644 js/src/layout/enums/index.ts create mode 100644 js/src/layout/index.ts create mode 100644 js/src/layout/types/LayoutParameters.type.ts create mode 100644 js/src/layout/types/index.ts diff --git a/js/src/layout/Layout.ts b/js/src/layout/Layout.ts new file mode 100644 index 00000000..8072b080 --- /dev/null +++ b/js/src/layout/Layout.ts @@ -0,0 +1,148 @@ +import { Fit, Alignment } from "./enums"; +import type { LayoutParameters } from "./types"; +import * as rc from "./../rive_advanced.mjs"; + +/** + * Represents the layout parameters for a Rive animation in a HTML canvas. + * The `Layout` class provides methods to manage the fit and alignment of the + * Rive animation within the canvas. + */ +export class Layout { + /** + * Caches the runtime fit value of the Rive animation to save computation cycles. + * + * Runtime fit and alignment are accessed every frame, so we cache their values to save cycles. + */ + private cachedRuntimeFit: rc.Fit; + private cachedRuntimeAlignment: rc.Alignment; + + /** + * The fit mode for the Rive animation within the canvas. This determines how the animation is scaled to fit the canvas. + */ + public readonly fit: Fit; + /** + * The alignment mode for the Rive animation within the canvas. This determines how the animation is positioned within the canvas. + */ + public readonly alignment: Alignment; + /** + * The minimum x-coordinate of the layout. + */ + public readonly minX: number; + /** + * The minimum y-coordinate of the layout. + */ + public readonly minY: number; + /** + * The maximum x-coordinate of the layout. + */ + public readonly maxX: number; + /** + * The maximum y-coordinate of the layout. + */ + public readonly maxY: number; + + /** + * Constructs a new `Layout` instance with the provided parameters. + */ + constructor(params?: LayoutParameters) { + this.fit = params?.fit ?? Fit.Contain; + this.alignment = params?.alignment ?? Alignment.Center; + this.minX = params?.minX ?? 0; + this.minY = params?.minY ?? 0; + this.maxX = params?.maxX ?? 0; + this.maxY = params?.maxY ?? 0; + } + + /** + * Alternative constructor to build a Layout from an interface/object. + * @deprecated + */ + static new({ + fit, + alignment, + minX, + minY, + maxX, + maxY, + }: LayoutParameters): Layout { + console.warn( + "This function is deprecated: please use `new Layout({})` instead" + ); + return new Layout({ fit, alignment, minX, minY, maxX, maxY }); + } + + /** + * Makes a copy of the layout, replacing any specified parameters + */ + public copyWith({ + fit, + alignment, + minX, + minY, + maxX, + maxY, + }: LayoutParameters): Layout { + return new Layout({ + fit: fit ?? this.fit, + alignment: alignment ?? this.alignment, + minX: minX ?? this.minX, + minY: minY ?? this.minY, + maxX: maxX ?? this.maxX, + maxY: maxY ?? this.maxY, + }); + } + + /** + * Returns the appropriate Fit value for the Rive runtime based on the `fit` property of the Layout. + * The result is cached to avoid unnecessary conversions. + * @param rive - The RiveCanvas instance to use for the Rive runtime fit values. + * @returns The Fit value corresponding to the `fit` property of the Layout. + */ + public runtimeFit(rive: rc.RiveCanvas): rc.Fit { + if (this.cachedRuntimeFit) return this.cachedRuntimeFit; + + let fit; + if (this.fit === Fit.Cover) fit = rive.Fit.cover; + else if (this.fit === Fit.Contain) fit = rive.Fit.contain; + else if (this.fit === Fit.Fill) fit = rive.Fit.fill; + else if (this.fit === Fit.FitWidth) fit = rive.Fit.fitWidth; + else if (this.fit === Fit.FitHeight) fit = rive.Fit.fitHeight; + else if (this.fit === Fit.ScaleDown) fit = rive.Fit.scaleDown; + else fit = rive.Fit.none; + + this.cachedRuntimeFit = fit; + return fit; + } + + /** + * Returns the appropriate Alignment value for the Rive runtime based on the `alignment` property of the Layout. + * The result is cached to avoid unnecessary conversions. + * @param rive - The RiveCanvas instance to use for the Rive runtime alignment values. + * @returns The Alignment value corresponding to the `alignment` property of the Layout. + */ + public runtimeAlignment(rive: rc.RiveCanvas): rc.Alignment { + if (this.cachedRuntimeAlignment) return this.cachedRuntimeAlignment; + + let alignment; + if (this.alignment === Alignment.TopLeft) + alignment = rive.Alignment.topLeft; + else if (this.alignment === Alignment.TopCenter) + alignment = rive.Alignment.topCenter; + else if (this.alignment === Alignment.TopRight) + alignment = rive.Alignment.topRight; + else if (this.alignment === Alignment.CenterLeft) + alignment = rive.Alignment.centerLeft; + else if (this.alignment === Alignment.CenterRight) + alignment = rive.Alignment.centerRight; + else if (this.alignment === Alignment.BottomLeft) + alignment = rive.Alignment.bottomLeft; + else if (this.alignment === Alignment.BottomCenter) + alignment = rive.Alignment.bottomCenter; + else if (this.alignment === Alignment.BottomRight) + alignment = rive.Alignment.bottomRight; + else alignment = rive.Alignment.center; + + this.cachedRuntimeAlignment = alignment; + return alignment; + } +} diff --git a/js/src/layout/enums/Alignment.enum.ts b/js/src/layout/enums/Alignment.enum.ts new file mode 100644 index 00000000..a7427f02 --- /dev/null +++ b/js/src/layout/enums/Alignment.enum.ts @@ -0,0 +1,41 @@ +/** + * Enum representing the different alignment options for the canvas. + */ +export enum Alignment { + /** + * Aligns the canvas content to the center. + */ + Center = "center", + /** + * Aligns the canvas content to the top-left corner. + */ + TopLeft = "topLeft", + /** + * Aligns the canvas content to the top center. + */ + TopCenter = "topCenter", + /** + * Aligns the canvas content to the top-right corner. + */ + TopRight = "topRight", + /** + * Aligns the canvas content to the center-left. + */ + CenterLeft = "centerLeft", + /** + * Aligns the canvas content to the center-right. + */ + CenterRight = "centerRight", + /** + * Aligns the canvas content to the bottom-left corner. + */ + BottomLeft = "bottomLeft", + /** + * Aligns the canvas content to the bottom center. + */ + BottomCenter = "bottomCenter", + /** + * Aligns the canvas content to the bottom-right corner. + */ + BottomRight = "bottomRight", +} diff --git a/js/src/layout/enums/Fit.enum.ts b/js/src/layout/enums/Fit.enum.ts new file mode 100644 index 00000000..03c47350 --- /dev/null +++ b/js/src/layout/enums/Fit.enum.ts @@ -0,0 +1,33 @@ +/** + * Enum representing the different fit options for a canvas element. + */ +export enum Fit { + /** + * Scales the image to fill the entire canvas, maintaining aspect ratio. + */ + Cover = "cover", + /** + * Scales the image to fit within the canvas, maintaining aspect ratio. + */ + Contain = "contain", + /** + * Stretches the image to fill the entire canvas, ignoring aspect ratio. + */ + Fill = "fill", + /** + * Scales the image to fit the width of the canvas, maintaining aspect ratio. + */ + FitWidth = "fitWidth", + /** + * Scales the image to fit the height of the canvas, maintaining aspect ratio. + */ + FitHeight = "fitHeight", + /** + * Displays the image at its original size. + */ + None = "none", + /** + * Scales down the image if it's larger than the canvas, but never scales it up. + */ + ScaleDown = "scaleDown", +} diff --git a/js/src/layout/enums/index.ts b/js/src/layout/enums/index.ts new file mode 100644 index 00000000..a06b3c8b --- /dev/null +++ b/js/src/layout/enums/index.ts @@ -0,0 +1,2 @@ +export { Alignment } from "./Alignment.enum"; +export { Fit } from "./Fit.enum"; diff --git a/js/src/layout/index.ts b/js/src/layout/index.ts new file mode 100644 index 00000000..4d1cf6f1 --- /dev/null +++ b/js/src/layout/index.ts @@ -0,0 +1,3 @@ +export * from "./Layout"; +export * from "./enums"; +export * from "./types"; diff --git a/js/src/layout/types/LayoutParameters.type.ts b/js/src/layout/types/LayoutParameters.type.ts new file mode 100644 index 00000000..432ff47f --- /dev/null +++ b/js/src/layout/types/LayoutParameters.type.ts @@ -0,0 +1,31 @@ +import type { Fit, Alignment } from "../enums"; + +/** + * Interface for the Layout static method contractor + */ +export interface LayoutParameters { + /** + * The fit mode for the layout. + */ + fit?: Fit; + /** + * The alignment mode for the layout. + */ + alignment?: Alignment; + /** + * The minimum X coordinate for the layout. + */ + minX?: number; + /** + * The minimum Y coordinate for the layout. + */ + minY?: number; + /** + * The maximum X coordinate for the layout. + */ + maxX?: number; + /** + * The maximum Y coordinate for the layout. + */ + maxY?: number; +} diff --git a/js/src/layout/types/index.ts b/js/src/layout/types/index.ts new file mode 100644 index 00000000..75cea482 --- /dev/null +++ b/js/src/layout/types/index.ts @@ -0,0 +1 @@ +export type { LayoutParameters } from "./LayoutParameters.type"; diff --git a/js/src/rive.ts b/js/src/rive.ts index f2e4d6c9..2cb0b468 100644 --- a/js/src/rive.ts +++ b/js/src/rive.ts @@ -1,8 +1,12 @@ import * as rc from "./rive_advanced.mjs"; import packageData from "package.json"; import { Animation } from "./animation"; +import { Layout } from "./layout"; import { registerTouchInteractions, sanitizeUrl, BLANK_URL } from "./utils"; +export * from "./animation"; +export * from "./layout"; + // Note: Re-exporting a few types from rive_advanced.mjs to expose for high-level // API usage without re-defining their type definition here. May want to revisit // and see if we want to expose both types from rive.ts and rive_advanced.mjs in @@ -32,148 +36,6 @@ interface SetupRiveListenersOptions { */ export type Bounds = rc.AABB; -// #region layout - -// Fit options for the canvas -export enum Fit { - Cover = "cover", - Contain = "contain", - Fill = "fill", - FitWidth = "fitWidth", - FitHeight = "fitHeight", - None = "none", - ScaleDown = "scaleDown", -} - -// Alignment options for the canvas -export enum Alignment { - Center = "center", - TopLeft = "topLeft", - TopCenter = "topCenter", - TopRight = "topRight", - CenterLeft = "centerLeft", - CenterRight = "centerRight", - BottomLeft = "bottomLeft", - BottomCenter = "bottomCenter", - BottomRight = "bottomRight", -} - -// Interface for the Layout static method contructor -export interface LayoutParameters { - fit?: Fit; - alignment?: Alignment; - minX?: number; - minY?: number; - maxX?: number; - maxY?: number; -} - -// Alignment options for Rive animations in a HTML canvas -export class Layout { - // Runtime fit and alignment are accessed every frame, so we cache their - // values to save cycles - private cachedRuntimeFit: rc.Fit; - private cachedRuntimeAlignment: rc.Alignment; - - public readonly fit: Fit; - public readonly alignment: Alignment; - public readonly minX: number; - public readonly minY: number; - public readonly maxX: number; - public readonly maxY: number; - - constructor(params?: LayoutParameters) { - this.fit = params?.fit ?? Fit.Contain; - this.alignment = params?.alignment ?? Alignment.Center; - this.minX = params?.minX ?? 0; - this.minY = params?.minY ?? 0; - this.maxX = params?.maxX ?? 0; - this.maxY = params?.maxY ?? 0; - } - - // Alternative constructor to build a Layout from an interface/object - static new({ - fit, - alignment, - minX, - minY, - maxX, - maxY, - }: LayoutParameters): Layout { - console.warn( - "This function is deprecated: please use `new Layout({})` instead", - ); - return new Layout({ fit, alignment, minX, minY, maxX, maxY }); - } - - /** - * Makes a copy of the layout, replacing any specified parameters - */ - public copyWith({ - fit, - alignment, - minX, - minY, - maxX, - maxY, - }: LayoutParameters): Layout { - return new Layout({ - fit: fit ?? this.fit, - alignment: alignment ?? this.alignment, - minX: minX ?? this.minX, - minY: minY ?? this.minY, - maxX: maxX ?? this.maxX, - maxY: maxY ?? this.maxY, - }); - } - - // Returns fit for the Wasm runtime format - public runtimeFit(rive: rc.RiveCanvas): rc.Fit { - if (this.cachedRuntimeFit) return this.cachedRuntimeFit; - - let fit; - if (this.fit === Fit.Cover) fit = rive.Fit.cover; - else if (this.fit === Fit.Contain) fit = rive.Fit.contain; - else if (this.fit === Fit.Fill) fit = rive.Fit.fill; - else if (this.fit === Fit.FitWidth) fit = rive.Fit.fitWidth; - else if (this.fit === Fit.FitHeight) fit = rive.Fit.fitHeight; - else if (this.fit === Fit.ScaleDown) fit = rive.Fit.scaleDown; - else fit = rive.Fit.none; - - this.cachedRuntimeFit = fit; - return fit; - } - - // Returns alignment for the Wasm runtime format - public runtimeAlignment(rive: rc.RiveCanvas): rc.Alignment { - if (this.cachedRuntimeAlignment) return this.cachedRuntimeAlignment; - - let alignment; - if (this.alignment === Alignment.TopLeft) - alignment = rive.Alignment.topLeft; - else if (this.alignment === Alignment.TopCenter) - alignment = rive.Alignment.topCenter; - else if (this.alignment === Alignment.TopRight) - alignment = rive.Alignment.topRight; - else if (this.alignment === Alignment.CenterLeft) - alignment = rive.Alignment.centerLeft; - else if (this.alignment === Alignment.CenterRight) - alignment = rive.Alignment.centerRight; - else if (this.alignment === Alignment.BottomLeft) - alignment = rive.Alignment.bottomLeft; - else if (this.alignment === Alignment.BottomCenter) - alignment = rive.Alignment.bottomCenter; - else if (this.alignment === Alignment.BottomRight) - alignment = rive.Alignment.bottomRight; - else alignment = rive.Alignment.center; - - this.cachedRuntimeAlignment = alignment; - return alignment; - } -} - -// #endregion - // #region runtime // Callback type when looking for a runtime instance