Maki map, colors and animations

This commit is contained in:
Jordan Eldredge 2021-06-27 14:05:42 -07:00
parent 5e98951c1e
commit 07154fdf9c
11 changed files with 198 additions and 35 deletions

View file

@ -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(

View file

@ -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");
}
}

View file

@ -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;
}
}

View 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})`;
}
}

View file

@ -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);
});
}
}

View file

@ -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 };
}

View file

@ -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();
*/
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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) {

View file

@ -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);
}