mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 10:15:31 +00:00
Maki map, colors and animations
This commit is contained in:
parent
5e98951c1e
commit
07154fdf9c
11 changed files with 198 additions and 35 deletions
|
|
@ -3,11 +3,14 @@ import { XmlElement } from "@rgrove/parse-xml";
|
|||
import TrueTypeFont from "./skin/TrueTypeFont";
|
||||
import { assert } from "./utils";
|
||||
import BitmapFont from "./skin/BitmapFont";
|
||||
import Color from "./skin/Color";
|
||||
|
||||
class UIRoot {
|
||||
// Just a temporary place to stash things
|
||||
_bitmaps: Bitmap[] = [];
|
||||
_bitmapImages: Map<string, HTMLImageElement> = new Map();
|
||||
_fonts: (TrueTypeFont | BitmapFont)[] = [];
|
||||
_colors: Color[] = [];
|
||||
_groupDefs: XmlElement[] = [];
|
||||
_xuiElements: XmlElement[] = [];
|
||||
|
||||
|
|
@ -26,9 +29,26 @@ class UIRoot {
|
|||
return found;
|
||||
}
|
||||
|
||||
addBitmapImage(id: string, image: HTMLImageElement) {
|
||||
this._bitmapImages.set(id, image);
|
||||
}
|
||||
|
||||
addFont(font: TrueTypeFont | BitmapFont) {
|
||||
this._fonts.push(font);
|
||||
}
|
||||
addColor(color: Color) {
|
||||
this._colors.push(color);
|
||||
}
|
||||
|
||||
getColor(id: string): Color {
|
||||
const lowercaseId = id.toLowerCase();
|
||||
const found = this._colors.find(
|
||||
(color) => color._id.toLowerCase() === lowercaseId
|
||||
);
|
||||
|
||||
assert(found != null, `Could not find color with id ${id}.`);
|
||||
return found;
|
||||
}
|
||||
|
||||
getFont(id: string): TrueTypeFont | BitmapFont {
|
||||
const found = this._fonts.find(
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import UI_ROOT from "../UIRoot";
|
||||
import { ensureVmInt, px } from "../utils";
|
||||
import Layer from "./Layer";
|
||||
import { VM } from "./VM";
|
||||
|
||||
export default class AnimatedLayer extends Layer {
|
||||
_currentFrame: number = 0;
|
||||
_frameCount: number = 0;
|
||||
_startFrame: number = 0;
|
||||
_endFrame: number = 0;
|
||||
_speed: number = 0;
|
||||
_animationInterval: NodeJS.Timeout | null = null;
|
||||
setXmlAttr(_key: string, value: string): boolean {
|
||||
const key = _key.toLowerCase();
|
||||
if (super.setXmlAttr(key, value)) {
|
||||
|
|
@ -14,36 +20,74 @@ export default class AnimatedLayer extends Layer {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_getImageHeight(): number {
|
||||
const bitmap = UI_ROOT.getBitmap(this._image);
|
||||
return bitmap.getHeight();
|
||||
}
|
||||
|
||||
getlength(): number {
|
||||
return this._frameCount;
|
||||
// TODO: What about other orientations?
|
||||
return this._getImageHeight() / this.getheight();
|
||||
}
|
||||
gotoframe(framenum: number) {
|
||||
this._currentFrame = framenum;
|
||||
this._currentFrame = ensureVmInt(framenum);
|
||||
this._renderFrame();
|
||||
// VM.dispatch(this, "onframe", [{ type: "INT", value: this._currentFrame }]);
|
||||
}
|
||||
getcurframe(): number {
|
||||
return this._currentFrame;
|
||||
}
|
||||
setstartframe(framenum: number) {
|
||||
// TODO
|
||||
this._startFrame = ensureVmInt(framenum);
|
||||
}
|
||||
setendframe(framenum: number) {
|
||||
// TODO
|
||||
this._endFrame = ensureVmInt(framenum);
|
||||
}
|
||||
setspeed(msperframe: number) {
|
||||
// TODO
|
||||
this._speed = msperframe;
|
||||
}
|
||||
play() {
|
||||
// TODO
|
||||
if (this._animationInterval != null) {
|
||||
clearInterval(this._animationInterval);
|
||||
this._animationInterval = null;
|
||||
}
|
||||
const end = this._endFrame;
|
||||
const start = this._startFrame;
|
||||
|
||||
const change = end > start ? 1 : -1;
|
||||
|
||||
let frame = this._startFrame;
|
||||
this.gotoframe(frame);
|
||||
if (frame === end) {
|
||||
return;
|
||||
}
|
||||
this._animationInterval = setInterval(() => {
|
||||
frame += change;
|
||||
this.gotoframe(frame);
|
||||
if (frame === end) {
|
||||
clearInterval(this._animationInterval);
|
||||
this._animationInterval = null;
|
||||
}
|
||||
}, this._speed);
|
||||
}
|
||||
pause() {
|
||||
// TODO
|
||||
}
|
||||
stop() {
|
||||
// TODO
|
||||
if (this._animationInterval != null) {
|
||||
clearInterval(this._animationInterval);
|
||||
this._animationInterval = null;
|
||||
}
|
||||
}
|
||||
isplaying(): boolean {
|
||||
// TODO
|
||||
return false;
|
||||
return this._animationInterval != null;
|
||||
}
|
||||
|
||||
_renderFrame() {
|
||||
this._div.style.backgroundPositionY = px(
|
||||
-(this._currentFrame * this.getheight())
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -63,6 +107,7 @@ extern AnimatedLayer.setRealtime(Boolean onoff);
|
|||
|
||||
draw() {
|
||||
super.draw();
|
||||
this._renderFrame();
|
||||
this._div.setAttribute("data-obj-name", "AnimatedLayer");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import * as Utils from "../utils";
|
||||
import { assert } from "../utils";
|
||||
import ImageManager from "./ImageManager";
|
||||
|
||||
export default class Bitmap {
|
||||
_id: string;
|
||||
_url: string;
|
||||
_img: HTMLImageElement;
|
||||
_canvas: HTMLCanvasElement;
|
||||
_x: number;
|
||||
_y: number;
|
||||
_width: number;
|
||||
|
|
@ -69,6 +73,8 @@ export default class Bitmap {
|
|||
);
|
||||
const imgUrl = await imageManager.getUrl(this._file);
|
||||
|
||||
this._img = await imageManager.getImage(imgUrl);
|
||||
|
||||
if (this._width == null && this._height == null) {
|
||||
const size = await imageManager.getSize(imgUrl);
|
||||
this.setXmlAttr("w", String(size.width));
|
||||
|
|
@ -93,4 +99,18 @@ export default class Bitmap {
|
|||
const height = Utils.px(this._height);
|
||||
return `${width} ${height}`;
|
||||
}
|
||||
|
||||
getCanvas(): HTMLCanvasElement {
|
||||
if (this._canvas == null) {
|
||||
assert(this._img != null, "Expected bitmap image to be loaded");
|
||||
this._canvas = document.createElement("canvas");
|
||||
this._canvas.width = this.getWidth();
|
||||
this._canvas.height = this.getHeight();
|
||||
const ctx = this._canvas.getContext("2d");
|
||||
ctx.drawImage(this._img, 0, 0, this.getWidth(), this.getHeight());
|
||||
document.body.appendChild(this._img);
|
||||
document.body.appendChild(this._canvas);
|
||||
}
|
||||
return this._canvas;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
packages/webamp-modern-2/src/skin/Color.ts
Normal file
39
packages/webamp-modern-2/src/skin/Color.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import ImageManager from "./ImageManager";
|
||||
|
||||
export default class Color {
|
||||
_id: string;
|
||||
_value: string;
|
||||
_gammagroup: string;
|
||||
|
||||
setXmlAttributes(attributes: { [attrName: string]: string }) {
|
||||
for (const [key, value] of Object.entries(attributes)) {
|
||||
this.setXmlAttr(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
setXmlAttr(_key: string, value: string) {
|
||||
const key = _key.toLowerCase();
|
||||
switch (key) {
|
||||
case "id":
|
||||
this._id = value;
|
||||
break;
|
||||
case "value":
|
||||
this._value = value;
|
||||
break;
|
||||
case "gammagroup":
|
||||
this._gammagroup = value;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getId() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
getRgb() {
|
||||
return `rgb(${this._value})`;
|
||||
}
|
||||
}
|
||||
|
|
@ -246,11 +246,11 @@ export default class GuiObj extends XmlObj {
|
|||
this._renderDimensions();
|
||||
|
||||
this._div.addEventListener("mouseup", (e) => {
|
||||
this.onLeftButtonUp(e.clientX, e.clientX);
|
||||
this.onLeftButtonUp(e.clientX, e.clientY);
|
||||
});
|
||||
|
||||
this._div.addEventListener("mousedown", (e) => {
|
||||
this.onLeftButtonDown(e.clientX, e.clientX);
|
||||
this.onLeftButtonDown(e.clientX, e.clientY);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ import { getCaseInsensitiveFile } from "../utils";
|
|||
|
||||
export default class ImageManager {
|
||||
_urlCache: Map<string, string>;
|
||||
_imgCache: Map<string, HTMLImageElement>;
|
||||
_sizeCache: Map<string, { width: number; height: number }>;
|
||||
constructor(private _zip: JSZip) {
|
||||
this._urlCache = new Map();
|
||||
this._sizeCache = new Map();
|
||||
this._imgCache = new Map();
|
||||
}
|
||||
|
||||
async getUrl(filePath: string): Promise<string | null> {
|
||||
|
|
@ -24,11 +26,20 @@ export default class ImageManager {
|
|||
|
||||
async getSize(url: string): Promise<{ width: number; height: number }> {
|
||||
if (!this._sizeCache.has(url)) {
|
||||
const size = await getImageSize(url);
|
||||
const size = await this.getImage(url);
|
||||
this._sizeCache.set(url, size);
|
||||
}
|
||||
return this._sizeCache.get(url);
|
||||
}
|
||||
|
||||
async getImage(url: string): Promise<HTMLImageElement> {
|
||||
if (!this._imgCache.has(url)) {
|
||||
// TODO: We could cache this
|
||||
const img = await loadImage(url);
|
||||
this._imgCache.set(url, img);
|
||||
}
|
||||
return this._imgCache.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
// This is intentionally async since we may want to sub it out for an async
|
||||
|
|
@ -48,9 +59,7 @@ async function getUrlFromBlob(blob: Blob): Promise<string> {
|
|||
});
|
||||
}
|
||||
|
||||
async function loadImage(
|
||||
imgUrl: string
|
||||
): Promise<{ width: number; height: number }> {
|
||||
async function loadImage(imgUrl: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.addEventListener("load", () => {
|
||||
|
|
@ -62,10 +71,3 @@ async function loadImage(
|
|||
img.src = imgUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function getImageSize(
|
||||
imgUrl: string
|
||||
): Promise<{ width: number; height: number }> {
|
||||
const { width, height } = await loadImage(imgUrl);
|
||||
return { width, height };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,39 @@
|
|||
import UI_ROOT from "../UIRoot";
|
||||
import { assert, assume } from "../utils";
|
||||
import BaseObject from "./BaseObject";
|
||||
import Bitmap from "./Bitmap";
|
||||
|
||||
export default class MakiMap extends BaseObject {
|
||||
loadmap(bitmapId: string) {}
|
||||
_bitmap: Bitmap;
|
||||
loadmap(bitmapId: string) {
|
||||
this._bitmap = UI_ROOT.getBitmap(bitmapId);
|
||||
}
|
||||
inregion(x: number, y: number): boolean {
|
||||
// TODO
|
||||
// Maybe this checks if the pixel is transparent?
|
||||
return true;
|
||||
}
|
||||
|
||||
// 0-255
|
||||
getvalue(x: number, y: number): number {
|
||||
// TODO
|
||||
return 12345;
|
||||
const canvas = this._bitmap.getCanvas();
|
||||
const context = canvas.getContext("2d");
|
||||
const { data } = context.getImageData(x, y, 1, 1);
|
||||
assert(
|
||||
data[0] === data[1] && data[0] === data[2],
|
||||
"Expected map image to be grey scale"
|
||||
);
|
||||
assume(data[3] === 255, "Expected map image not have transparency");
|
||||
return data[0];
|
||||
}
|
||||
getwidth(): number {
|
||||
return this._bitmap.getWidth();
|
||||
}
|
||||
geheight(): number {
|
||||
return this._bitmap.getHeight();
|
||||
}
|
||||
|
||||
/*
|
||||
extern Int Map.getValue(int x, int y);
|
||||
extern Int Map.getARGBValue(int x, int y, int channel); // requires wa 5.51 // channel: 0=Blue, 1=Green, 2=Red, 3=Alpha. if your img has a alpha channal the returned rgb value might not be exact
|
||||
extern Boolean Map.inRegion(int x, int y);
|
||||
extern Map.loadMap(String bitmapid);
|
||||
extern Int Map.getWidth();
|
||||
extern Int Map.getHeight();
|
||||
extern Region Map.getRegion();
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export default class Text extends GuiObj {
|
|||
_align: string;
|
||||
_font: string;
|
||||
_fontSize: number;
|
||||
_color: string;
|
||||
setXmlAttr(key: string, value: string): boolean {
|
||||
if (super.setXmlAttr(key, value)) {
|
||||
return true;
|
||||
|
|
@ -50,13 +51,16 @@ export default class Text extends GuiObj {
|
|||
case "fontsize":
|
||||
// (int) The size to render the chosen font.
|
||||
this._fontSize = Utils.num(value);
|
||||
|
||||
case "color":
|
||||
// (int[sic?]) The comma delimited RGB color of the text.
|
||||
this._color = value;
|
||||
/*
|
||||
ticker - (bool) Setting this flag causes the object to scroll left and right if the text does not fit the rectangular area of the text object.
|
||||
antialias - (bool) Setting this flag causes the text to be rendered antialiased if possible.
|
||||
default - (str) A parameter alias for text.
|
||||
align - (str) One of the following three possible strings: "left" "center" "right" -- Default is "left."
|
||||
valign - (str) One of the following three possible strings: "top" "center" "bottom" -- Default is "top."
|
||||
color - (int) The comma delimited RGB color of the text.
|
||||
shadowcolor - (int) The comma delimited RGB color for underrendered shadow text.
|
||||
shadowx - (int) The x offset of the shadowrender.
|
||||
shadowy - (int) The y offset of the shadowrender.
|
||||
|
|
@ -121,6 +125,13 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
|
|||
this._div.style.fontFamily = font.getFontFamily();
|
||||
}
|
||||
|
||||
if (this._color) {
|
||||
console.log(this._color);
|
||||
const color = UI_ROOT.getColor(this._color);
|
||||
console.log({ color });
|
||||
this._div.style.color = color.getRbg();
|
||||
}
|
||||
|
||||
this._div.style.fontSize = Utils.px(this._fontSize ?? 14);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ class Vm {
|
|||
script.methods[binding.methodOffset].name === event &&
|
||||
script.variables[binding.variableOffset].value === object
|
||||
) {
|
||||
this.interpret(scriptId, binding.commandOffset, args);
|
||||
const reversedArgs = [...args].reverse();
|
||||
this.interpret(scriptId, binding.commandOffset, reversedArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import GuiObj from "./GuiObj";
|
|||
import AnimatedLayer from "./AnimatedLayer";
|
||||
import Vis from "./Vis";
|
||||
import BitmapFont from "./BitmapFont";
|
||||
import Color from "./Color";
|
||||
|
||||
class ParserContext {
|
||||
container: Container | null = null;
|
||||
|
|
@ -317,7 +318,10 @@ export default class SkinParser {
|
|||
"Unexpected children in <color> XML node."
|
||||
);
|
||||
|
||||
// TODO: Parse colors
|
||||
const color = new Color();
|
||||
color.setXmlAttributes(node.attributes);
|
||||
|
||||
UI_ROOT.addColor(color);
|
||||
}
|
||||
|
||||
async slider(node: XmlElement) {
|
||||
|
|
|
|||
|
|
@ -39,3 +39,8 @@ let id = 0;
|
|||
export function getId(): number {
|
||||
return id++;
|
||||
}
|
||||
|
||||
// TODO: Delete this once we have proper type coersion in the VM.
|
||||
export function ensureVmInt(num: number): number {
|
||||
return Math.floor(num);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue