Skip to content

Commit

Permalink
feat: add responsive layout properties and other fixes
Browse files Browse the repository at this point in the history
This PR:
- Fixes hit testing when providing a `layoutScaleFactor`
- Exposes `artboardWidth`, `artboardHeight`, and `devicePixelRatio` on the `Rive` object. This is needed for more advanced user control, and is also needed in Rive-React to complete the layout implementation.
- Adds a bunch of tests for the above, and expected responsive behaviour under various conditions.

Diffs=
86b7531b20 feat: add responsive layout properties and other fixes (#8451)

Co-authored-by: Gordon <[email protected]>
  • Loading branch information
HayesGordon and HayesGordon committed Oct 31, 2024
1 parent 0f38187 commit e9364ca
Show file tree
Hide file tree
Showing 6 changed files with 358 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5297cf0cc25ef5b45d438dddaefb2ac52cb8c4a1
86b7531b209507a61cc8aa5fac2ea9bf89668d2d
110 changes: 107 additions & 3 deletions js/src/rive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,13 @@ export class Rive {
// Keep a local value of the set volume to update it asynchronously
private _volume = 1;

// Keep a local value of the set width to update it asynchronously
private _artboardWidth: number | undefined = undefined;

// Keep a local value of the set height to update it asynchronously
private _artboardHeight: number | undefined = undefined;

// Keep a local value of the device pixel ratio used in rendering and canvas/artboard resizing
private _devicePixelRatioUsed = 1;

// Whether the canvas element's size is 0
Expand Down Expand Up @@ -1700,6 +1707,7 @@ export class Rive {
fit: this._layout.runtimeFit(this.runtime),
alignment: this._layout.runtimeAlignment(this.runtime),
isTouchScrollEnabled: touchScrollEnabledOption,
layoutScaleFactor: this._layout.layoutScaleFactor,
});
}
}
Expand Down Expand Up @@ -1730,6 +1738,16 @@ export class Rive {
}
}

private initArtboardSize() {
if (!this.artboard) return;

// Use preset values if they are not undefined
this._artboardWidth = this.artboard.width =
this._artboardWidth || this.artboard.width;
this._artboardHeight = this.artboard.height =
this._artboardHeight || this.artboard.height;
}

// Initializes runtime with Rive data and preps for playing
private async initData(
artboardName: string,
Expand All @@ -1756,6 +1774,9 @@ export class Rive {
autoplay,
);

// Initialize the artboard size
this.initArtboardSize();

// Check for audio
this.initializeAudio();

Expand Down Expand Up @@ -2280,13 +2301,18 @@ export class Rive {
* Accounts for devicePixelRatio as a multiplier to render the size of the canvas drawing surface.
* Uses the size of the backing canvas to set new width/height attributes. Need to re-render
* and resize the layout to match the new drawing surface afterwards.
* Useful function for consumers to include in a window resize listener
* Useful function for consumers to include in a window resize listener.
*
* This method will set the {@link devicePixelRatioUsed} property.
*
* Optionally, you can provide a {@link customDevicePixelRatio} to provide a
* custom value.
*/
public resizeDrawingSurfaceToCanvas(customDevicePixelRatio?: number) {
if (this.canvas instanceof HTMLCanvasElement && !!window) {
const { width, height } = this.canvas.getBoundingClientRect();
const dpr = customDevicePixelRatio || window.devicePixelRatio || 1;
this._devicePixelRatioUsed = dpr;
this.devicePixelRatioUsed = dpr;
this.canvas.width = dpr * width;
this.canvas.height = dpr * height;
this.startRendering();
Expand Down Expand Up @@ -2761,7 +2787,7 @@ export class Rive {
}

/**
* getter and setter for the volume of the artboard
* Getter / Setter for the volume of the artboard
*/
public get volume(): number {
if (this.artboard && this.artboard.volume !== this._volume) {
Expand All @@ -2776,6 +2802,84 @@ export class Rive {
this.artboard.volume = value * audioManager.systemVolume;
}
}

/**
* The width of the artboard.
*
* This will return undefined if the artboard is not loaded yet and a custom
* width has not been set.
*
* Do not set this value manually when using {@link resizeDrawingSurfaceToCanvas}
* with a {@link Layout.fit} of {@link Fit.Layout}, as the artboard width is
* automatically set.
*/
public get artboardWidth(): number | undefined {
if (this.artboard) {
return this.artboard.width;
}
return this._artboardWidth;
}

public set artboardWidth(value: number) {
this._artboardWidth = value;
if (this.artboard) {
this.artboard.width = value;
}
}

/**
* The height of the artboard.
*
* This will return undefined if the artboard is not loaded yet and a custom
* height has not been set.
*
* Do not set this value manually when using {@link resizeDrawingSurfaceToCanvas}
* with a {@link Layout.fit} of {@link Fit.Layout}, as the artboard height is
* automatically set.
*/
public get artboardHeight(): number | undefined {
if (this.artboard) {
return this.artboard.height;
}
return this._artboardHeight;
}

public set artboardHeight(value: number) {
this._artboardHeight = value;

if (this.artboard) {
this.artboard.height = value;
}
}

/**
* Reset the artboard size to its original values.
*/
public resetArtboardSize() {
if (this.artboard) {
this.artboard.resetArtboardSize();
this._artboardWidth = this.artboard.width;
this._artboardHeight = this.artboard.height;
} else {
// If the artboard isn't loaded, we need to reset the custom width and height
this._artboardWidth = undefined;
this._artboardHeight = undefined;
}
}

/**
* The device pixel ratio used in rendering and canvas/artboard resizing.
*
* This value will be overidden by the device pixel ratio used in
* {@link resizeDrawingSurfaceToCanvas}. If you use that method, do not set this value.
*/
public get devicePixelRatioUsed(): number {
return this._devicePixelRatioUsed;
}

public set devicePixelRatioUsed(value: number) {
this._devicePixelRatioUsed = value;
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions js/src/utils/registerTouchInteractions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface TouchInteractionsParams {
fit: rc.Fit;
alignment: rc.Alignment;
isTouchScrollEnabled?: boolean;
layoutScaleFactor?: number;
}

interface ClientCoordinates {
Expand Down Expand Up @@ -69,6 +70,7 @@ export const registerTouchInteractions = ({
fit,
alignment,
isTouchScrollEnabled = false,
layoutScaleFactor = 1.0,
}: TouchInteractionsParams) => {
if (
!canvas ||
Expand Down Expand Up @@ -142,6 +144,7 @@ export const registerTouchInteractions = ({
maxY: boundingRect.height,
},
artboard.bounds,
layoutScaleFactor,
);
const invertedMatrix = new rive.Mat2D();
forwardMatrix.invert(invertedMatrix);
Expand Down
28 changes: 28 additions & 0 deletions js/test/artboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,32 @@ test("Artboard bounds can be retrieved from a loaded Rive file", (done) => {
});
});

test("Artboard width and height can be get/set from a loaded Rive file", (done) => {
const canvas = document.createElement("canvas");
const r = new rive.Rive({
canvas: canvas,
artboard: "MyArtboard",
buffer: stateMachineFileBuffer,
onLoad: () => {
const initialWidth = r.artboardWidth;
const initialHeight = r.artboardHeight;

expect(initialWidth).toBe(500);
expect(initialHeight).toBe(500);

r.artboardWidth = 1000;
r.artboardHeight = 1000;

expect(r.artboardWidth).toBe(1000);
expect(r.artboardHeight).toBe(1000);

r.resetArtboardSize();

expect(r.artboardWidth).toBe(initialWidth);
expect(r.artboardHeight).toBe(initialHeight);
done();
},
});
});

// #endregion
27 changes: 27 additions & 0 deletions js/test/layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,31 @@ test("Layouts can be copied with overridden values", (): void => {
expect(layout.maxY).toBe(40);
});

test("New Layout with Fit.Layout works as expected", (): void => {
let layout = new rive.Layout({
fit: rive.Fit.Layout,
layoutScaleFactor: 2,
});

expect(layout.fit).toBe(rive.Fit.Layout);
expect(layout.layoutScaleFactor).toBe(2);
});

test("layoutScaleFactor can be copied with overridden values", (): void => {
let layout = new rive.Layout({
fit: rive.Fit.Layout,
layoutScaleFactor: 2,
});

expect(layout.fit).toBe(rive.Fit.Layout);
expect(layout.layoutScaleFactor).toBe(2);

layout = layout.copyWith({
layoutScaleFactor: 3,
});

expect(layout.fit).toBe(rive.Fit.Layout);
expect(layout.layoutScaleFactor).toBe(3);
});

// #endregion
Loading

0 comments on commit e9364ca

Please sign in to comment.