mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 02:57:30 +00:00
Render animated layers, still needs image loading changes (#900)
* Render animated layers, still needs image loading changes * convert params from strings once initially, add return types * rename params->style * fix gotoframe * split out defaults/type conversion into own functions, review tweaks * fix more null checks * abstract out backgroundPosition and width/height calculation * use raf for animation loop, split out into functions * fix lint errors, specify window as global
This commit is contained in:
parent
803b89d9e0
commit
c4adc4f2b7
3 changed files with 242 additions and 56 deletions
|
|
@ -321,6 +321,86 @@ function Layer({ node, id, image, x, y }) {
|
|||
);
|
||||
}
|
||||
|
||||
function animatedLayerOffsetAndSize(
|
||||
frameNum,
|
||||
frameSize,
|
||||
layerSize,
|
||||
imgSize,
|
||||
imgOffset
|
||||
) {
|
||||
let size, offset;
|
||||
if (frameSize !== undefined) {
|
||||
size = Number(frameSize);
|
||||
offset = -Number(frameSize) * frameNum;
|
||||
} else if (layerSize !== undefined) {
|
||||
size = Number(layerSize);
|
||||
offset = -Number(layerSize) * frameNum;
|
||||
} else {
|
||||
if (imgSize !== undefined) {
|
||||
size = Number(imgSize);
|
||||
}
|
||||
if (imgOffset !== undefined) {
|
||||
offset = -Number(imgOffset);
|
||||
}
|
||||
}
|
||||
return { offset, size };
|
||||
}
|
||||
|
||||
function AnimatedLayer({
|
||||
node,
|
||||
id,
|
||||
image,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
framewidth,
|
||||
frameheight,
|
||||
}) {
|
||||
if (image == null) {
|
||||
console.warn("Got an AnimatedLayer without an image. Rendering null", id);
|
||||
return null;
|
||||
}
|
||||
|
||||
const img = node.js_imageLookup(image.toLowerCase());
|
||||
const frameNum = node.getcurframe();
|
||||
|
||||
let style = {};
|
||||
if (x !== undefined) {
|
||||
style.left = Number(x);
|
||||
}
|
||||
if (y !== undefined) {
|
||||
style.top = Number(y);
|
||||
}
|
||||
|
||||
const {
|
||||
offset: backgroundPositionX,
|
||||
size: width,
|
||||
} = animatedLayerOffsetAndSize(frameNum, framewidth, w, img.w, img.x);
|
||||
const {
|
||||
offset: backgroundPositionY,
|
||||
size: height,
|
||||
} = animatedLayerOffsetAndSize(frameNum, frameheight, h, img.h, img.y);
|
||||
style = { ...style, width, height, backgroundPositionX, backgroundPositionY };
|
||||
|
||||
if (img.imgUrl !== undefined) {
|
||||
style.backgroundImage = `url(${img.imgUrl}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<GuiObjectEvents node={node}>
|
||||
<div
|
||||
data-node-type="AnimatedLayer"
|
||||
data-node-id={id}
|
||||
draggable={false}
|
||||
style={{ position: "absolute", ...style }}
|
||||
>
|
||||
<XmlChildren node={node} />
|
||||
</div>
|
||||
</GuiObjectEvents>
|
||||
);
|
||||
}
|
||||
|
||||
function Button({
|
||||
id,
|
||||
image,
|
||||
|
|
@ -510,6 +590,7 @@ const NODE_NAME_TO_COMPONENT = {
|
|||
group: Group,
|
||||
popupmenu: Popupmenu,
|
||||
text: Text,
|
||||
animatedlayer: AnimatedLayer,
|
||||
};
|
||||
|
||||
function DummyComponent({ node }) {
|
||||
|
|
@ -528,15 +609,21 @@ function XmlChildren({ node }) {
|
|||
|
||||
// Given a skin XML node, pick which component to use, and render it.
|
||||
function XmlNode({ node }) {
|
||||
const { name } = node;
|
||||
let { name } = node;
|
||||
if (name == null) {
|
||||
// name is null is likely a comment
|
||||
return null;
|
||||
}
|
||||
name = name.toLowerCase();
|
||||
if (
|
||||
name == null ||
|
||||
name === "groupdef" ||
|
||||
name === "elements" ||
|
||||
name === "gammaset" ||
|
||||
name === "scripts"
|
||||
name === "scripts" ||
|
||||
name === "script" ||
|
||||
name === "skininfo"
|
||||
) {
|
||||
// name is null is likely a comment
|
||||
// these nodes dont need to be rendered
|
||||
return null;
|
||||
}
|
||||
useJsUpdates(node);
|
||||
|
|
|
|||
|
|
@ -346,28 +346,14 @@ Set {
|
|||
"Button.onleftclick",
|
||||
"Button.onrightclick",
|
||||
"Button.setactivatednocallback",
|
||||
"AnimatedLayer.play",
|
||||
"AnimatedLayer.pause",
|
||||
"AnimatedLayer.stop",
|
||||
"AnimatedLayer.setspeed",
|
||||
"AnimatedLayer.gotoframe",
|
||||
"AnimatedLayer.getlength",
|
||||
"AnimatedLayer.onplay",
|
||||
"AnimatedLayer.onpause",
|
||||
"AnimatedLayer.onresume",
|
||||
"AnimatedLayer.onstop",
|
||||
"AnimatedLayer.onframe",
|
||||
"AnimatedLayer.setstartframe",
|
||||
"AnimatedLayer.setendframe",
|
||||
"AnimatedLayer.setautoreplay",
|
||||
"AnimatedLayer.isplaying",
|
||||
"AnimatedLayer.ispaused",
|
||||
"AnimatedLayer.isstopped",
|
||||
"AnimatedLayer.getstartframe",
|
||||
"AnimatedLayer.getendframe",
|
||||
"AnimatedLayer.getdirection",
|
||||
"AnimatedLayer.getautoreplay",
|
||||
"AnimatedLayer.getcurframe",
|
||||
"AnimatedLayer.setrealtime",
|
||||
"ToggleButton.ontoggle",
|
||||
"ToggleButton.getcurcfgval",
|
||||
|
|
|
|||
|
|
@ -2,39 +2,159 @@ import Layer from "./Layer";
|
|||
import { unimplementedWarning } from "../utils";
|
||||
|
||||
class AnimatedLayer extends Layer {
|
||||
_playing: boolean;
|
||||
_frameNum: number;
|
||||
_animationStartTime: number;
|
||||
|
||||
constructor(node, parent, annotations, store) {
|
||||
super(node, parent, annotations, store);
|
||||
|
||||
this._setAttributeDefaults(this.attributes);
|
||||
this._convertAttributeTypes(this.attributes);
|
||||
this._initializeStartAndEnd(this.attributes);
|
||||
|
||||
this._playing = this.attributes.autoplay;
|
||||
this._frameNum = this.attributes.start || 0;
|
||||
this._animationStartTime = 0;
|
||||
|
||||
this._setupAnimationLoop();
|
||||
}
|
||||
|
||||
_setAttributeDefaults(attributes: Object): void {
|
||||
if (attributes.autoplay == null) {
|
||||
attributes.autoplay = "0";
|
||||
}
|
||||
if (attributes.autoreplay == null) {
|
||||
attributes.autoreplay = "1";
|
||||
}
|
||||
if (attributes.speed == null) {
|
||||
attributes.speed = "200";
|
||||
}
|
||||
}
|
||||
|
||||
_convertAttributeTypes(attributes: Object): void {
|
||||
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(attributes: Object): void {
|
||||
if (attributes.start != null && attributes.end != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const image = this.js_imageLookup(attributes.image);
|
||||
if (!image) {
|
||||
console.warn("Could not find image: ", attributes.image);
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.start == null) {
|
||||
attributes.start = 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_animationLoop() {
|
||||
window.requestAnimationFrame(() => {
|
||||
const currentTime = window.performance.now();
|
||||
if (currentTime > this._animationStartTime + this.attributes.speed) {
|
||||
this._animationStartTime = currentTime;
|
||||
this.js_trigger("js_framechange");
|
||||
} else {
|
||||
this._animationLoop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_setupAnimationLoop() {
|
||||
this.js_listen("js_framechange", () => {
|
||||
this._frameNum += 1;
|
||||
if (this._frameNum > this.getendframe()) {
|
||||
this._frameNum = this.getstartframe();
|
||||
if (!this.attributes.autoreplay) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.js_trigger("js_update");
|
||||
|
||||
if (this._playing) {
|
||||
this._animationLoop();
|
||||
}
|
||||
});
|
||||
|
||||
if (this._playing) {
|
||||
this.js_trigger("js_framechange");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getclassname()
|
||||
*
|
||||
* Returns the class name for the object.
|
||||
* @ret The class name.
|
||||
*/
|
||||
getclassname() {
|
||||
getclassname(): string {
|
||||
return "AnimatedLayer";
|
||||
}
|
||||
|
||||
play() {
|
||||
unimplementedWarning("play");
|
||||
play(): void {
|
||||
// TODO: do we need to trigger something for `onplay`/`onresume` events?
|
||||
this._playing = true;
|
||||
this.js_trigger("js_framechange");
|
||||
}
|
||||
|
||||
pause() {
|
||||
unimplementedWarning("pause");
|
||||
pause(): void {
|
||||
// TODO: do we need to trigger something for `onpause` events?
|
||||
this._playing = false;
|
||||
}
|
||||
|
||||
stop() {
|
||||
unimplementedWarning("stop");
|
||||
stop(): void {
|
||||
// TODO: do we need to trigger something for `onstop` events?
|
||||
this._playing = false;
|
||||
this._frameNum = this.getstartframe();
|
||||
}
|
||||
|
||||
setspeed(msperframe: number) {
|
||||
unimplementedWarning("setspeed");
|
||||
setspeed(msperframe: number): void {
|
||||
this.attributes.speed = msperframe;
|
||||
}
|
||||
|
||||
gotoframe(framenum: number) {
|
||||
unimplementedWarning("gotoframe");
|
||||
gotoframe(framenum: number): void {
|
||||
this._frameNum = framenum;
|
||||
this.js_trigger("js_update");
|
||||
}
|
||||
|
||||
getlength() {
|
||||
unimplementedWarning("getlength");
|
||||
return 10;
|
||||
getlength(): number {
|
||||
return this.getendframe() - this.getstartframe();
|
||||
}
|
||||
|
||||
onplay() {
|
||||
|
|
@ -62,24 +182,21 @@ class AnimatedLayer extends Layer {
|
|||
return;
|
||||
}
|
||||
|
||||
setstartframe(framenum: number) {
|
||||
unimplementedWarning("setstartframe");
|
||||
return;
|
||||
setstartframe(framenum: number): void {
|
||||
this.attributes.start = framenum;
|
||||
}
|
||||
|
||||
setendframe(framenum: number) {
|
||||
unimplementedWarning("setendframe");
|
||||
return;
|
||||
setendframe(framenum: number): void {
|
||||
this.attributes.end = framenum;
|
||||
}
|
||||
|
||||
setautoreplay(onoff: boolean) {
|
||||
unimplementedWarning("setautoreplay");
|
||||
return;
|
||||
setautoreplay(onoff: boolean): void {
|
||||
// TODO: should this trigger the animation if it isn't currently runnnig?
|
||||
this.attributes.autoreplay = onoff;
|
||||
}
|
||||
|
||||
isplaying() {
|
||||
unimplementedWarning("isplaying");
|
||||
return;
|
||||
isplaying(): boolean {
|
||||
return this._playing;
|
||||
}
|
||||
|
||||
ispaused() {
|
||||
|
|
@ -92,14 +209,12 @@ class AnimatedLayer extends Layer {
|
|||
return;
|
||||
}
|
||||
|
||||
getstartframe() {
|
||||
unimplementedWarning("getstartframe");
|
||||
return;
|
||||
getstartframe(): number {
|
||||
return this.attributes.start;
|
||||
}
|
||||
|
||||
getendframe() {
|
||||
unimplementedWarning("getendframe");
|
||||
return;
|
||||
getendframe(): number {
|
||||
return this.attributes.end;
|
||||
}
|
||||
|
||||
getdirection() {
|
||||
|
|
@ -107,14 +222,12 @@ class AnimatedLayer extends Layer {
|
|||
return;
|
||||
}
|
||||
|
||||
getautoreplay() {
|
||||
unimplementedWarning("getautoreplay");
|
||||
return;
|
||||
getautoreplay(): boolean {
|
||||
return this.attributes.autoreplay;
|
||||
}
|
||||
|
||||
getcurframe() {
|
||||
unimplementedWarning("getcurframe");
|
||||
return;
|
||||
getcurframe(): number {
|
||||
return this._frameNum;
|
||||
}
|
||||
|
||||
setrealtime(onoff: boolean) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue