mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 18:25:30 +00:00
Type AnimatedLayer (#939)
This commit is contained in:
parent
108ae57c10
commit
26ffb91ab3
2 changed files with 92 additions and 71 deletions
|
|
@ -3,98 +3,119 @@ import { unimplementedWarning } from "../utils";
|
|||
import { XmlNode } from "../types";
|
||||
import MakiObject from "./MakiObject";
|
||||
|
||||
// Eventually we will want to type the attributes for each MakiObject. To do
|
||||
// that we will need patterns and utilities that make it easy. This is a first
|
||||
// stab at a pattern with a few utility functions. The idea is that the
|
||||
// attributes we expect are all typed as optional strings, since I think all XML
|
||||
// attributes are optional. Then we have utility functions which handle type
|
||||
// coersion with default values.
|
||||
//
|
||||
// My goal is that the XML attributes parsed in
|
||||
// the constructor and pulled into typed local properties.
|
||||
type JSImage = {
|
||||
imgUrl: string;
|
||||
h: number;
|
||||
w: number;
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
type Attributes = {
|
||||
js_assets: {
|
||||
image: JSImage;
|
||||
};
|
||||
end?: string;
|
||||
frameheight?: string;
|
||||
framewidth?: string;
|
||||
autoplay?: string;
|
||||
autoreplay?: string;
|
||||
start?: string;
|
||||
speed?: string;
|
||||
h?: string;
|
||||
w?: string;
|
||||
};
|
||||
|
||||
function getNumber(attr: string | undefined, fallback: number): number {
|
||||
return attr === undefined ? fallback : Number(attr);
|
||||
}
|
||||
|
||||
function getBoolean(attr: string | undefined, fallback: boolean): boolean {
|
||||
// TODO: Check if there are othe values that parse as `true`, there probably are.
|
||||
return attr === undefined ? fallback : attr === "1";
|
||||
}
|
||||
|
||||
class AnimatedLayer extends Layer {
|
||||
_typedAttributes: Attributes;
|
||||
_playing: boolean;
|
||||
_autoReplay: boolean;
|
||||
_start: number;
|
||||
_end: number;
|
||||
_speed: number;
|
||||
_frameNum: number;
|
||||
_animationStartTime: number;
|
||||
_animationCancelID: number | null;
|
||||
|
||||
constructor(node: XmlNode, parent: MakiObject, annotations: Object = {}) {
|
||||
super(node, parent, annotations);
|
||||
// @ts-ignore For now MakiObject defines a much-too-generic version of
|
||||
// attributes. Eventually we will want to be able to define the XML node we
|
||||
// expect locally. For now we will just cheat.
|
||||
this._typedAttributes = this.attributes;
|
||||
const { autoplay, autoreplay, start, speed } = this._typedAttributes;
|
||||
|
||||
this._setAttributeDefaults();
|
||||
this._convertAttributeTypes();
|
||||
this._initializeStartAndEnd();
|
||||
this._playing = getBoolean(autoplay, true);
|
||||
this._autoReplay = getBoolean(autoreplay, true);
|
||||
this._frameNum = getNumber(start, 0);
|
||||
this._start = getNumber(start, 0);
|
||||
this._speed = getNumber(speed, 200);
|
||||
this._end = this._initializeEnd();
|
||||
|
||||
this._playing = Boolean(this.attributes.autoplay);
|
||||
this._frameNum = this.attributes.start || 0;
|
||||
this._animationStartTime = 0;
|
||||
this._animationCancelID = null;
|
||||
|
||||
this._setupAnimationLoop();
|
||||
}
|
||||
|
||||
_setAttributeDefaults(): void {
|
||||
const { attributes } = this;
|
||||
if (attributes.autoplay == null) {
|
||||
attributes.autoplay = "0";
|
||||
}
|
||||
if (attributes.autoreplay == null) {
|
||||
attributes.autoreplay = "1";
|
||||
}
|
||||
if (attributes.speed == null) {
|
||||
attributes.speed = "200";
|
||||
}
|
||||
}
|
||||
_initializeEnd(): number {
|
||||
const {
|
||||
end,
|
||||
js_assets,
|
||||
frameheight,
|
||||
framewidth,
|
||||
w,
|
||||
h,
|
||||
} = this._typedAttributes;
|
||||
|
||||
_convertAttributeTypes(): void {
|
||||
const { attributes } = this;
|
||||
if (attributes.autoplay != null) {
|
||||
attributes.autoplay = !!Number(attributes.autoplay);
|
||||
}
|
||||
if (attributes.autoreplay != null) {
|
||||
attributes.autoreplay = !!Number(attributes.autoreplay);
|
||||
}
|
||||
if (attributes.speed != null) {
|
||||
attributes.speed = Number(attributes.speed);
|
||||
}
|
||||
if (attributes.start != null) {
|
||||
attributes.start = Number(attributes.start);
|
||||
}
|
||||
if (attributes.end != null) {
|
||||
attributes.end = Number(attributes.end);
|
||||
}
|
||||
}
|
||||
|
||||
_initializeStartAndEnd(): void {
|
||||
const { attributes } = this;
|
||||
if (attributes.start != null && attributes.end != null) {
|
||||
return;
|
||||
if (end != null) {
|
||||
return getNumber(end, 0);
|
||||
}
|
||||
|
||||
const image = attributes.js_assets.image;
|
||||
const { image } = js_assets;
|
||||
if (!image) {
|
||||
console.warn("Could not find image: ", attributes.image);
|
||||
return;
|
||||
console.warn("Could not find js_assets image");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (attributes.start == null) {
|
||||
attributes.start = 0;
|
||||
}
|
||||
const typedFrameHeight = getNumber(frameheight, 0);
|
||||
const typedFrameWidth = getNumber(framewidth, 0);
|
||||
|
||||
if (attributes.end == null) {
|
||||
if (attributes.frameheight != null) {
|
||||
attributes.end = Math.ceil(image.h / attributes.frameheight);
|
||||
} else if (attributes.framewidth != null) {
|
||||
attributes.end = Math.ceil(image.w / attributes.framewidth);
|
||||
} else {
|
||||
// In the general case where we don't have a frameheight/framewidth and
|
||||
// the start/end are not both set, we calculate the end frame by
|
||||
// calculating the end in both directions and picking the longer repeat length
|
||||
const width = attributes.w != null ? attributes.w : image.w;
|
||||
const height = attributes.h != null ? attributes.h : image.h;
|
||||
attributes.end = Math.max(
|
||||
Math.ceil(image.w / width),
|
||||
Math.ceil(image.h / height)
|
||||
);
|
||||
}
|
||||
if (typedFrameHeight !== 0) {
|
||||
return Math.ceil(image.h / typedFrameHeight);
|
||||
} else if (typedFrameWidth !== 0) {
|
||||
return Math.ceil(image.w / typedFrameWidth);
|
||||
}
|
||||
// In the general case where we don't have a frameheight/framewidth and
|
||||
// the start/end are not both set, we calculate the end frame by
|
||||
// calculating the end in both directions and picking the longer repeat length
|
||||
const width = getNumber(w, image.w);
|
||||
const height = getNumber(h, image.h);
|
||||
return Math.max(Math.ceil(image.w / width), Math.ceil(image.h / height));
|
||||
}
|
||||
|
||||
_animationLoop() {
|
||||
this._animationCancelID = window.requestAnimationFrame(() => {
|
||||
const currentTime = window.performance.now();
|
||||
if (currentTime > this._animationStartTime + this.attributes.speed) {
|
||||
if (currentTime > this._animationStartTime + this._speed) {
|
||||
this._animationStartTime = currentTime;
|
||||
this.js_trigger("js_framechange");
|
||||
} else {
|
||||
|
|
@ -108,7 +129,7 @@ class AnimatedLayer extends Layer {
|
|||
this._frameNum += 1;
|
||||
if (this._frameNum > this.getendframe()) {
|
||||
this._frameNum = this.getstartframe();
|
||||
if (!this.attributes.autoreplay) {
|
||||
if (!this._autoReplay) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -159,7 +180,7 @@ class AnimatedLayer extends Layer {
|
|||
}
|
||||
|
||||
setspeed(msperframe: number): void {
|
||||
this.attributes.speed = msperframe;
|
||||
this._speed = msperframe;
|
||||
}
|
||||
|
||||
gotoframe(framenum: number): void {
|
||||
|
|
@ -192,7 +213,7 @@ class AnimatedLayer extends Layer {
|
|||
}
|
||||
|
||||
setstartframe(framenum: number): void {
|
||||
this.attributes.start = framenum;
|
||||
this._start = framenum;
|
||||
}
|
||||
|
||||
setendframe(framenum: number): void {
|
||||
|
|
@ -201,7 +222,7 @@ class AnimatedLayer extends Layer {
|
|||
|
||||
setautoreplay(onoff: boolean): void {
|
||||
// TODO: should this trigger the animation if it isn't currently runnnig?
|
||||
this.attributes.autoreplay = onoff;
|
||||
this._autoReplay = onoff;
|
||||
}
|
||||
|
||||
isplaying(): boolean {
|
||||
|
|
@ -219,11 +240,11 @@ class AnimatedLayer extends Layer {
|
|||
}
|
||||
|
||||
getstartframe(): number {
|
||||
return this.attributes.start;
|
||||
return this._start;
|
||||
}
|
||||
|
||||
getendframe(): number {
|
||||
return this.attributes.end;
|
||||
return this._end;
|
||||
}
|
||||
|
||||
getdirection() {
|
||||
|
|
@ -232,7 +253,7 @@ class AnimatedLayer extends Layer {
|
|||
}
|
||||
|
||||
getautoreplay(): boolean {
|
||||
return this.attributes.autoreplay;
|
||||
return this._autoReplay;
|
||||
}
|
||||
|
||||
getcurframe(): number {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"js/**/*.tsx",
|
||||
"modern/src/Emitter.ts",
|
||||
"modern/src/runtime/AlbumArtLayer.ts",
|
||||
// "modern/src/runtime/AnimatedLayer.ts",
|
||||
"modern/src/runtime/AnimatedLayer.ts",
|
||||
"modern/src/runtime/BitList.ts",
|
||||
"modern/src/runtime/Browser.ts",
|
||||
"modern/src/runtime/Button.ts",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue