mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 11:04:00 +00:00
Stub out Layers, Groups, Containers and Layouts
This commit is contained in:
parent
a46363dcfe
commit
395cbfbcb9
13 changed files with 384 additions and 71 deletions
1
packages/webamp-modern-2/assets/.gitignore
vendored
Normal file
1
packages/webamp-modern-2/assets/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
CornerAmp_Redux/
|
||||
BIN
packages/webamp-modern-2/assets/CornerAmp_Redux.zip
Normal file
BIN
packages/webamp-modern-2/assets/CornerAmp_Redux.zip
Normal file
Binary file not shown.
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
34
packages/webamp-modern-2/src/skin/Container.ts
Normal file
34
packages/webamp-modern-2/src/skin/Container.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
54
packages/webamp-modern-2/src/skin/Group.ts
Normal file
54
packages/webamp-modern-2/src/skin/Group.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
56
packages/webamp-modern-2/src/skin/GuiObj.ts
Normal file
56
packages/webamp-modern-2/src/skin/GuiObj.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
35
packages/webamp-modern-2/src/skin/Layer.ts
Normal file
35
packages/webamp-modern-2/src/skin/Layer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
41
packages/webamp-modern-2/src/skin/Layout.ts
Normal file
41
packages/webamp-modern-2/src/skin/Layout.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
17
packages/webamp-modern-2/src/skin/XmlObj.ts
Normal file
17
packages/webamp-modern-2/src/skin/XmlObj.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue