Stub out Layers, Groups, Containers and Layouts

This commit is contained in:
Jordan Eldredge 2021-06-12 23:46:34 -07:00
parent a46363dcfe
commit 395cbfbcb9
13 changed files with 384 additions and 71 deletions

View file

@ -0,0 +1 @@
CornerAmp_Redux/

Binary file not shown.

View file

@ -1 +1,24 @@
export default class UIRoot {}
import Bitmap from "./skin/Bitmap";
import { assert } from "./utils";
class UIRoot {
// Just a temporary place to stash things
_bitmaps: Bitmap[] = [];
addBitmap(bitmap: Bitmap) {
this._bitmaps.push(bitmap);
}
getBitmap(id: string): Bitmap {
const found = this._bitmaps.find(
(bitmap) => bitmap._id.toLowerCase() === id.toLowerCase()
);
assert(found != null, `Could not find bitmap with id ${id}.`);
return found;
}
}
// Global Singleton for now
const UI_ROOT = new UIRoot();
export default UI_ROOT;

View file

@ -9,6 +9,10 @@ async function main() {
const parser = new SkinParser(zip);
await parser.parse();
for (const container of parser._containers) {
document.body.appendChild(container.getDebugDom());
}
}
main();

View file

@ -1,3 +1,5 @@
import * as Utils from "../utils";
import ImageManager from "./ImageManager";
export default class Bitmap {
_id: string;
_url: string;
@ -5,27 +7,74 @@ export default class Bitmap {
_y: number;
_width: number;
_height: number;
_file: string;
_gammagroup: string;
constructor({
id,
url,
x,
y,
width,
height,
}: {
id: string;
url: string;
x: number;
y: number;
width: number;
height: number;
}) {
this._id = id;
setXmlAttributes(attributes: { [attrName: string]: string }) {
for (const [key, value] of Object.entries(attributes)) {
this.setXmlAttr(key, value);
}
}
setXmlAttr(key: string, value: string) {
switch (key) {
case "id":
this._id = value;
break;
case "x":
this._x = Utils.num(value) ?? 0;
break;
case "y":
this._y = Utils.num(value) ?? 0;
break;
case "w":
this._width = Utils.num(value);
break;
case "h":
this._height = Utils.num(value);
break;
case "file":
this._file = value;
case "gammagroup":
this._gammagroup = value;
break;
default:
console.log({ key });
}
}
getWidth() {
return this._width;
}
getHeight() {
return this._height;
}
setUrl(url: string) {
this._url = url;
this._x = x;
this._y = y;
this._width = width;
this._height = height;
}
// Ensure we've loaded the image into our image loader.
async ensureImageLoaded(imageManager: ImageManager) {
Utils.assert(
this._url == null,
"Tried to ensure a Bitmap was laoded more than once."
);
const imgUrl = await imageManager.getUrl(this._file);
if (this._width == null && this._height == null) {
const size = await imageManager.getSize(imgUrl);
this.setXmlAttr("w", String(size.width));
this.setXmlAttr("h", String(size.height));
}
this.setUrl(imgUrl);
}
getBackgrondCSSAttribute(): string {
const width = Utils.px(this._width);
const height = Utils.px(this._height);
return `url(${this._url}) ${width} ${height}`;
}
}

View file

@ -0,0 +1,34 @@
import Group from "./Group";
import Layout from "./Layout";
import XmlObj from "./XmlObj";
export default class Container extends XmlObj {
_layouts: Layout[] = [];
constructor() {
super();
}
setXmlAttr(key: string, value: string): boolean {
if (super.setXmlAttr(key, value)) {
return true;
}
switch (key) {
default:
return false;
}
return true;
}
addLayout(layout: Layout) {
layout.setParent(this);
this._layouts.push(layout);
}
getDebugDom(): HTMLDivElement {
const div = window.document.createElement("div");
for (const layout of this._layouts) {
div.appendChild(layout.getDebugDom());
}
return div;
}
}

View file

@ -0,0 +1,54 @@
import * as Utils from "../utils";
import UI_ROOT from "../UIRoot";
import GuiObj from "./GuiObj";
export default class Group extends GuiObj {
_background: string;
_desktopAlpha: boolean;
_drawBackground: boolean;
_minimumHeight: number;
_maximumHeight: number;
_minimumWidth: number;
_maximumWidth: number;
// TODO: Some of these might belong back on Layout
setXmlAttr(key: string, value: string): boolean {
if (super.setXmlAttr(key, value)) {
return true;
}
switch (key) {
case "background":
this._background = value;
break;
case "drawbackground":
this._drawBackground = Utils.toBool(value);
break;
case "minimum_h":
this._minimumHeight = Utils.num(value);
break;
case "minimum_w":
this._minimumWidth = Utils.num(value);
break;
case "maximum_h":
this._maximumHeight = Utils.num(value);
break;
case "maximum_w":
this._maximumWidth = Utils.num(value);
break;
default:
return false;
}
return true;
}
getDebugDom(): HTMLDivElement {
const div = super.getDebugDom();
div.style.height = Utils.px(this._maximumHeight);
div.style.width = Utils.px(this._maximumWidth);
if (this._background != null) {
const bitmap = UI_ROOT.getBitmap(this._background);
div.style.background = bitmap.getBackgrondCSSAttribute();
}
return div;
}
}

View file

@ -0,0 +1,56 @@
import * as Utils from "../utils";
import XmlObj from "./XmlObj";
export default class GuiObj extends XmlObj {
_id: string;
_width: number;
_height: number;
_x: number = 0;
_y: number = 0;
_droptarget: string;
setXmlAttr(key: string, value: string): boolean {
switch (key) {
case "id":
this._id = value;
break;
case "w":
this._width = Utils.num(value);
break;
case "h":
this._height = Utils.num(value);
break;
case "x":
this._x = Utils.num(value) ?? 0;
break;
case "y":
this._y = Utils.num(value) ?? 0;
break;
case "droptarget":
this._droptarget = value;
break;
default:
return false;
}
return true;
}
getDebugDom(): HTMLDivElement {
const div = window.document.createElement("div");
div.style.display = "inline-block";
div.style.position = "absolute";
if (this._x) {
div.style.left = Utils.px(this._x);
}
if (this._y) {
div.style.top = Utils.px(this._y);
}
if (this._width) {
div.style.width = Utils.px(this._width);
}
if (this._height) {
div.style.height = Utils.px(this._height);
}
return div;
}
}

View file

@ -0,0 +1,35 @@
import GuiObj from "./GuiObj";
import UI_ROOT from "../UIRoot";
import { px } from "../utils";
export default class Layer extends GuiObj {
_image: string;
setXmlAttr(key: string, value: string): boolean {
if (super.setXmlAttr(key, value)) {
return true;
}
switch (key) {
case "image":
this._image = value;
default:
return false;
}
return true;
}
getDebugDom(): HTMLDivElement {
const div = super.getDebugDom();
if (this._image != null) {
const bitmap = UI_ROOT.getBitmap(this._image);
div.style.background = bitmap.getBackgrondCSSAttribute();
if (div.style.width === "" && bitmap.getWidth()) {
div.style.width = px(bitmap.getWidth());
}
if (div.style.height === "" && bitmap.getHeight()) {
div.style.height = px(bitmap.getHeight());
}
}
return div;
}
}

View file

@ -0,0 +1,41 @@
import Group from "./Group";
import * as Utils from "../utils";
import Container from "./Container";
import Layer from "./Layer";
export default class Layout extends Group {
_parent: Container | null = null;
// TODO: I don't think Layers are actually children of Layouts
_layers: Layer[] = [];
setXmlAttr(key: string, value: string): boolean {
if (super.setXmlAttr(key, value)) {
return true;
}
switch (key) {
case "desktopalpha":
this._desktopAlpha = Utils.toBool(value);
break;
default:
return false;
}
return true;
}
setParent(container: Container) {
this._parent = container;
}
// TODO: I don't think this is right.
addLayer(layer: Layer) {
this._layers.push(layer);
}
getDebugDom(): HTMLDivElement {
const div = super.getDebugDom();
for (const layer of this._layers) {
div.appendChild(layer.getDebugDom());
}
return div;
}
}

View file

@ -0,0 +1,17 @@
import * as Utils from "../utils";
import UI_ROOT from "../UIRoot";
export default class XmlObj {
_id: string;
_droptarget: string;
setXmlAttributes(attributes: { [attrName: string]: string }) {
for (const [key, value] of Object.entries(attributes)) {
this.setXmlAttr(key, value);
}
}
setXmlAttr(_key: string, _value: string): boolean {
return false;
}
}

View file

@ -1,20 +1,28 @@
import parseXml, { XmlElement } from "@rgrove/parse-xml";
import { assert, num, getCaseInsensitiveFile, px } from "../utils";
import UIRoot from "../UIRoot";
import parseXml, { XmlDocument, XmlElement } from "@rgrove/parse-xml";
import { assert, num, getCaseInsensitiveFile, px, toBool } from "../utils";
import UI_ROOT from "../UIRoot";
import JSZip, { JSZipObject } from "jszip";
import Bitmap from "./Bitmap";
import ImageManager from "./ImageManager";
import Layout from "./Layout";
import Group from "./Group";
import Container from "./Container";
import Layer from "./Layer";
class ParserContext {
container: Container | null = null;
layout: Layout | null = null;
}
export default class SkinParser {
_zip: JSZip;
_imageManager: ImageManager;
_path: string[];
_root: UIRoot;
_path: string[] = [];
_context: ParserContext = new ParserContext();
_containers: Container[] = [];
constructor(zip: JSZip) {
this._zip = zip;
this._path = [];
this._root = new UIRoot();
this._imageManager = new ImageManager(zip);
}
async parse() {
@ -26,16 +34,16 @@ export default class SkinParser {
// A different XML parser library might make this unnessesary.
const parsed = parseXml(includedXml);
this.traverseChildren(parsed);
await this.traverseChildren(parsed);
}
async traverseChildren(parent) {
async traverseChildren(parent: XmlElement | XmlDocument) {
for (const child of parent.children) {
if (child instanceof XmlElement) {
await this.traverseChild(child);
}
}
}
async traverseChild(node) {
async traverseChild(node: XmlElement) {
switch (node.name.toLowerCase()) {
case "wasabixml":
return this.wasabiXml(node);
@ -119,6 +127,8 @@ export default class SkinParser {
}
async group(node: XmlElement) {
const group = new Group();
group.setXmlAttributes(node.attributes);
await this.traverseChildren(node);
}
@ -127,46 +137,11 @@ export default class SkinParser {
node.children.length === 0,
"Unexpected children in <bitmap> XML node."
);
const { file } = node.attributes;
assert(file != null, "Expected bitmap node to have a `file` attribute");
const bitmap = new Bitmap();
bitmap.setXmlAttributes(node.attributes);
await bitmap.ensureImageLoaded(this._imageManager);
const imgUrl = await this._imageManager.getUrl(file);
assert(imgUrl != null, `Could not find bitmap at path ${file}`);
const id = node.attributes.id;
const x = num(node.attributes.x) ?? 0;
const y = num(node.attributes.y) ?? 0;
let width = num(node.attributes.w);
let height = num(node.attributes.h);
if (width == null || height == null) {
assert(
x != null && y != null,
"Expected images with unknown size to not have offsets."
);
assert(
width == null && height == null,
"Expected both dimensions to be missing."
);
const size = await this._imageManager.getSize(imgUrl);
width = size.width;
height = size.height;
}
// prettier-ignore
const bitmap = new Bitmap({ url: imgUrl, id, x, y, width, height });
// TODO: Store this somewhere. For now, we can just show it.
const div = document.createElement("div");
div.style.height = px(bitmap._height);
div.style.width = px(bitmap._width);
div.style.backgroundImage = `url(${bitmap._url})`;
div.style.backgroundPositionX = px(-bitmap._x);
div.style.backgroundPositionY = px(-bitmap._y);
div.style.display = "inline-block";
div.style.imageRendering = "pixelated";
document.body.appendChild(div);
UI_ROOT.addBitmap(bitmap);
}
async text(node: XmlElement) {
@ -250,10 +225,25 @@ export default class SkinParser {
}
async layer(node: XmlElement) {
const layer = new Layer();
layer.setXmlAttributes(node.attributes);
const { layout } = this._context;
if (layout == null) {
console.warn("FIXME: Expected <Layer> to be within a <layout>");
return;
}
layout.addLayer(layer);
await this.traverseChildren(node);
}
async layout(node: XmlElement) {
const layout = new Layout();
layout.setXmlAttributes(node.attributes);
const { container } = this._context;
assert(container != null, "Expected <Layout> to be in a <container>");
container.addLayout(layout);
this._context.layout = layout;
await this.traverseChildren(node);
}
@ -270,6 +260,10 @@ export default class SkinParser {
}
async container(node: XmlElement) {
const container = new Container();
container.setXmlAttributes(node.attributes);
this._context.container = container;
this._containers.push(container);
await this.traverseChildren(node);
}
@ -349,7 +343,7 @@ export default class SkinParser {
// A different XML parser library might make this unnessesary.
const parsed = parseXml(`<wrapper>${includedXml}</wrapper>`);
this.traverseChildren(parsed);
await this.traverseChildren(parsed);
for (const _dir of directories) {
this._path.pop();

View file

@ -20,3 +20,8 @@ export function num(str: string | void): number | null {
export function px(size: number): string {
return `${size}px`;
}
export function toBool(str: string) {
assert(str === "0" || str === "1", 'Expected bool value to be "0" or "1".');
return str === "1";
}