diff --git a/packages/webamp-modern-2/README.md b/packages/webamp-modern-2/README.md index 0651190d..ee60be11 100644 --- a/packages/webamp-modern-2/README.md +++ b/packages/webamp-modern-2/README.md @@ -1,5 +1,6 @@ # TODO Next +- [ ] Fix all `// FIXME` - [ ] SystemObject.getruntimeversion - [ ] SystemObject.getskinname - [ ] Ensure only wrapped variables go on the stack diff --git a/packages/webamp-modern-2/src/index.ts b/packages/webamp-modern-2/src/index.ts index 3021304f..60b186d5 100644 --- a/packages/webamp-modern-2/src/index.ts +++ b/packages/webamp-modern-2/src/index.ts @@ -11,12 +11,24 @@ async function main() { await parser.parse(); for (const container of parser._containers) { - container.init(); + container.init({ containers: parser._containers }); } + let node = document.createElement("div"); + for (const container of parser._containers) { - document.body.appendChild(container.getDebugDom()); + node.appendChild(container.getDebugDom()); } + + document.body.appendChild(node); + setInterval(() => { + document.body.removeChild(node); + node = document.createElement("div"); + for (const container of parser._containers) { + node.appendChild(container.getDebugDom()); + } + document.body.appendChild(node); + }, 1000); } main(); diff --git a/packages/webamp-modern-2/src/maki/interpreter.ts b/packages/webamp-modern-2/src/maki/interpreter.ts index 3a74fef4..b932ebab 100644 --- a/packages/webamp-modern-2/src/maki/interpreter.ts +++ b/packages/webamp-modern-2/src/maki/interpreter.ts @@ -18,6 +18,7 @@ class Interpreter { variables: Variable[]; methods: Method[]; commands: Command[]; + debug: boolean; classResolver: (guid: string) => any; constructor(program: ParsedMaki, classResolver: (guid: string) => any) { const { commands, methods, variables, classes } = program; @@ -29,6 +30,7 @@ class Interpreter { this.stack = []; this.callStack = []; + this.debug = false; } interpret(start: number) { @@ -36,6 +38,9 @@ class Interpreter { let ip = start; while (ip < this.commands.length) { const command = this.commands[ip]; + if (this.debug) { + console.log(command); + } switch (command.opcode) { // push @@ -70,7 +75,7 @@ class Interpreter { a.type == b.type, `Tried to compare a ${a.type} to a ${b.type}.` ); - const result = V.newInt(a.value === b.value); + const result = V.newInt(b.value === a.value); this.stack.push(result); break; } @@ -82,7 +87,7 @@ class Interpreter { a.type == b.type, `Tried to compare a ${a.type} to a ${b.type}.` ); - const result = V.newInt(a.value !== b.value); + const result = V.newInt(b.value !== a.value); this.stack.push(result); break; } @@ -104,7 +109,10 @@ class Interpreter { case "NULL": throw new Error("Tried to add non-numbers."); } - this.stack.push(V.newInt(a.value > b.value)); + if (this.debug) { + console.log(`${b.value} > ${a.value}`); + } + this.stack.push(V.newInt(b.value > a.value)); break; } // >= @@ -130,7 +138,11 @@ class Interpreter { case "NULL": throw new Error("Tried to add non-numbers."); } - this.stack.push(V.newInt(a.value < b.value)); + if (this.debug) { + console.log(`${b.value} < ${a.value}`); + } + + this.stack.push(V.newInt(b.value < a.value)); break; } // <= @@ -184,7 +196,7 @@ class Interpreter { // actually having access to the object instance. if (!klass.prototype[methodName]) { throw new Error( - `Need to add missing method "${methodName}" to ${klass.name}` + `Need to add missing method: ${klass.name}.${methodName}: ${returnType}` ); } let argCount = klass.prototype[methodName].length; @@ -200,13 +212,18 @@ class Interpreter { "Tried to call a method on a primitive." ); let value = obj.value[methodName](...methodArgs); - if (typeof value.then === "function") { - throw new Error("Did not expect maki method to return promise"); + if (value === undefined && returnType !== "NULL") { + throw new Error( + `Did not expect ${klass.name}.${methodName}: ${returnType} to return undefined` + ); } if (value === null) { // variables[1] holds global NULL value value = this.variables[1]; } + if (this.debug) { + console.log(`Calling method ${methodName}`); + } this.stack.push({ type: returnType, value } as any); break; } @@ -234,7 +251,6 @@ class Interpreter { case 48: { const a = this.stack.pop(); const b = this.stack.pop(); - assume( a.type === b.type, `Type mismatch: ${a.type} != ${b.type} at ip: ${ip}` @@ -319,7 +335,7 @@ class Interpreter { throw new Error("Tried to add non-numbers."); } // TODO: Do we need to round the value if INT? - this.stack.push({ type: a.type, value: a.value + b.value }); + this.stack.push({ type: a.type, value: b.value + a.value }); break; } // - (subtract) @@ -341,7 +357,7 @@ class Interpreter { throw new Error("Tried to add non-numbers."); } // TODO: Do we need to round the value if INT? - this.stack.push({ type: a.type, value: a.value - b.value }); + this.stack.push({ type: a.type, value: b.value - a.value }); break; } // * (multiply) @@ -363,7 +379,7 @@ class Interpreter { throw new Error("Tried to add non-numbers."); } // TODO: Do we need to round the value if INT? - this.stack.push({ type: a.type, value: a.value * b.value }); + this.stack.push({ type: a.type, value: b.value * a.value }); break; } // / (divide) @@ -384,7 +400,7 @@ class Interpreter { throw new Error("Tried to add non-numbers."); } // TODO: Do we need to round the value if INT? - this.stack.push({ type: a.type, value: a.value / b.value }); + this.stack.push({ type: a.type, value: b.value / a.value }); break; } // % (mod) @@ -451,7 +467,28 @@ class Interpreter { } // logAnd (&&) case 80: { - assume(false, "Unimplimented && operator"); + const a = this.stack.pop(); + const b = this.stack.pop(); + // Some of these are probably valid, but we'll enable them once we see usage. + switch (a.type) { + case "STRING": + case "OBJECT": + case "BOOL": + case "NULL": + throw new Error("Tried to add non-numbers."); + } + switch (b.type) { + case "STRING": + case "OBJECT": + case "BOOL": + case "NULL": + throw new Error("Tried to add non-numbers."); + } + if (b.value && a.value) { + this.stack.push(a); + } else { + this.stack.push(b); + } break; } // logOr || @@ -473,10 +510,12 @@ class Interpreter { case "NULL": throw new Error("Tried to add non-numbers."); } - if (a.value) { - return a; + if (b.value) { + this.stack.push(b); + } else { + this.stack.push(a); } - return b; + break; } // << case 88: { @@ -494,7 +533,7 @@ class Interpreter { const guid = this.classes[classesOffset]; const Klass = this.classResolver(guid); const klassInst = new Klass(); - this.stack.push(klassInst); + this.stack.push({ type: "OBJECT", value: klassInst }); break; } // delete diff --git a/packages/webamp-modern-2/src/maki/parser.ts b/packages/webamp-modern-2/src/maki/parser.ts index 685fb694..fed68991 100644 --- a/packages/webamp-modern-2/src/maki/parser.ts +++ b/packages/webamp-modern-2/src/maki/parser.ts @@ -26,6 +26,7 @@ export type ParsedMaki = { export type Binding = { commandOffset: number; + methodOffset: number; }; const MAGIC = "FG"; diff --git a/packages/webamp-modern-2/src/skin/BaseObject.ts b/packages/webamp-modern-2/src/skin/BaseObject.ts new file mode 100644 index 00000000..f27b96b1 --- /dev/null +++ b/packages/webamp-modern-2/src/skin/BaseObject.ts @@ -0,0 +1,21 @@ +/** + Object Class. + + @short This is the base class from which all other classes inherit. + @author Nullsoft Inc. + @ver 1.0 +*/ +export default class BaseObject { + /** + * Returns the class name for the object. + * + * @ret The class name. + */ + getClassName(): string { + throw new Error("Unimplemented"); + } + + getId() { + throw new Error("Unimplemented"); + } +} diff --git a/packages/webamp-modern-2/src/skin/Button.ts b/packages/webamp-modern-2/src/skin/Button.ts new file mode 100644 index 00000000..345d6c12 --- /dev/null +++ b/packages/webamp-modern-2/src/skin/Button.ts @@ -0,0 +1,26 @@ +import GuiObj from "./GuiObj"; + +// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cbutton.2F.3E_.26_.3Ctogglebutton.2F.3E +export default class Button extends GuiObj { + setXmlAttr(key: string, value: string): boolean { + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + default: + return false; + } + return true; + } + + /* + extern Button.onActivate(int activated); + extern Button.onLeftClick(); + extern Button.onRightClick(); + extern Button.setActivated(Boolean onoff); + extern Button.setActivatedNoCallback(Boolean onoff); + extern Boolean Button.getActivated(); + extern Button.leftClick(); + extern Button.rightClick();e + */ +} diff --git a/packages/webamp-modern-2/src/skin/Container.ts b/packages/webamp-modern-2/src/skin/Container.ts index b1e46e4b..967fbb5d 100644 --- a/packages/webamp-modern-2/src/skin/Container.ts +++ b/packages/webamp-modern-2/src/skin/Container.ts @@ -1,5 +1,7 @@ +import { SkinContext } from "../types"; import { toBool } from "../utils"; import Group from "./Group"; +import Layer from "./Layer"; import Layout from "./Layout"; import XmlObj from "./XmlObj"; @@ -13,6 +15,7 @@ export default class Container extends XmlObj { _layouts: Layout[] = []; _activeLayout: Layout | null = null; _defaultVisible: boolean = true; + _id: string; constructor() { super(); } @@ -22,6 +25,9 @@ export default class Container extends XmlObj { return true; } switch (key) { + case "id": + this._id = value.toLowerCase(); + break; case "default_visible": this._defaultVisible = toBool(value); break; @@ -31,12 +37,35 @@ export default class Container extends XmlObj { return true; } - init() { + init(context: SkinContext) { for (const layout of this._layouts) { - layout.init(); + layout.init(context); } } + getId() { + return this._id; + } + + /* Required for Maki */ + /** + * Get the layout associated with the an id. + * This corresponds to the "id=..." attribute in + * the XML tag . + * + * @ret The layout associated with the id. + * @param layout_id The id of the layout you wish to retrieve. + */ + getlayout(layoutId: string): Layout { + const lower = layoutId.toLowerCase(); + for (const layout of this._layouts) { + if (layout.getId() === lower) { + return layout; + } + } + throw new Error(`Could not find a container with the id; "${layoutId}"`); + } + addLayout(layout: Layout) { layout.setParent(this); this._layouts.push(layout); diff --git a/packages/webamp-modern-2/src/skin/Group.ts b/packages/webamp-modern-2/src/skin/Group.ts index e7f862c0..a38957a9 100644 --- a/packages/webamp-modern-2/src/skin/Group.ts +++ b/packages/webamp-modern-2/src/skin/Group.ts @@ -2,6 +2,7 @@ import * as Utils from "../utils"; import UI_ROOT from "../UIRoot"; import GuiObj from "./GuiObj"; import SystemObject from "./SystemObject"; +import { SkinContext } from "../types"; export default class Group extends GuiObj { _background: string; @@ -43,12 +44,12 @@ export default class Group extends GuiObj { return true; } - init() { + init(context: SkinContext) { for (const systemObject of this._systemObjects) { - systemObject.init(); + systemObject.init(context); } for (const child of this._children) { - child.init(); + child.init(context); } } @@ -61,6 +62,18 @@ export default class Group extends GuiObj { this._children.push(child); } + /* Required for Maki */ + + getobject(objectId: string): GuiObj { + const lower = objectId.toLowerCase(); + for (const obj of this._children) { + if (obj.getId() === lower) { + return obj; + } + } + throw new Error(`Could not find an object with the id; "${objectId}"`); + } + getDebugDom(): HTMLDivElement { const div = super.getDebugDom(); div.style.height = Utils.px(this._maximumHeight); diff --git a/packages/webamp-modern-2/src/skin/GuiObj.ts b/packages/webamp-modern-2/src/skin/GuiObj.ts index 8aa9261f..17a3df15 100644 --- a/packages/webamp-modern-2/src/skin/GuiObj.ts +++ b/packages/webamp-modern-2/src/skin/GuiObj.ts @@ -1,3 +1,4 @@ +import { SkinContext } from "../types"; import * as Utils from "../utils"; import XmlObj from "./XmlObj"; @@ -10,11 +11,12 @@ export default class GuiObj extends XmlObj { _y: number = 0; _droptarget: string; _visible: boolean = true; + _dirty: boolean = false; setXmlAttr(key: string, value: string): boolean { switch (key) { case "id": - this._id = value; + this._id = value.toLowerCase(); break; case "w": this._width = Utils.num(value); @@ -40,10 +42,76 @@ export default class GuiObj extends XmlObj { return true; } - init() { + init(context: SkinContext) { // pass } + getId(): string { + return this._id; + } + + /** + * Trigger the show event. + */ + show() { + this._visible = true; + this._dirty = true; + } + + /** + * Trigger the hide event. + */ + hide() { + this._visible = false; + this._dirty = true; + } + + /** + * Get the Y position, in the screen, of the + * top edge of the object. + * + * @ret The top edge's position (in screen coordinates). + */ + gettop(): number { + return this._x; + } + + /** + * Get the height of the object, in pixels. + * + * @ret The height of the object. + */ + getheight() { + // FIXME + return this._height || 100; + } + + /** + * Get the width of the object, in pixels. + * + * @ret The width of the object. + */ + getwidth() { + // FIXME + return this._width || 100; + } + + /** + * Resize the object to the desired size and position. + * + * @param x The X position where to anchor the object before resize. + * @param y The Y position where to anchor the object before resize. + * @param w The width you wish the object to have. + * @param h The height you wish the object to have. + */ + resize(x: number, y: number, w: number, h: number) { + this._x = x; + this._y = y; + this._width = w; + this._height = h; + this._dirty = true; + } + getDebugDom(): HTMLDivElement { const div = window.document.createElement("div"); div.style.display = this._visible ? "inline-block" : "none"; diff --git a/packages/webamp-modern-2/src/skin/PopupMenu.ts b/packages/webamp-modern-2/src/skin/PopupMenu.ts new file mode 100644 index 00000000..49bb2b3b --- /dev/null +++ b/packages/webamp-modern-2/src/skin/PopupMenu.ts @@ -0,0 +1,19 @@ +import BaseObject from "./BaseObject"; + +export default class PopupMenu extends BaseObject { + addcommand( + cmdText: string, + cmd_id: number, + checked: boolean, + disabled: boolean + ) {} + addseparator() {} + /* +extern PopupMenu.addSubMenu(PopupMenu submenu, String submenutext); +extern Int PopupMenu.popAtXY(int x, int y); +extern Int PopupMenu.popAtMouse(); +extern Int PopupMenu.getNumCommands(); +extern PopupMenu.checkCommand(int cmd_id, boolean check); +extern PopupMenu.disableCommand(int cmd_id, boolean disable); + */ +} diff --git a/packages/webamp-modern-2/src/skin/Slider.ts b/packages/webamp-modern-2/src/skin/Slider.ts new file mode 100644 index 00000000..b3988c4b --- /dev/null +++ b/packages/webamp-modern-2/src/skin/Slider.ts @@ -0,0 +1,25 @@ +import GuiObj from "./GuiObj"; + +// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cslider.2F.3E_.26_.3CWasabi:HSlider.2F.3E_.26_.3CWasabi:VSlider.2F.3E +export default class Slider extends GuiObj { + setXmlAttr(key: string, value: string): boolean { + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + default: + return false; + } + return true; + } + + /* + extern Slider.onSetPosition(int newpos); + extern Slider.onPostedPosition(int newpos); + extern Slider.onSetFinalPosition(int pos); + extern Slider.setPosition(int pos); + extern Int Slider.getPosition(); + extern Slider.lock(); // locks descendant core collbacks + extern Slider.unlock(); // unloads the + */ +} diff --git a/packages/webamp-modern-2/src/skin/Status.ts b/packages/webamp-modern-2/src/skin/Status.ts new file mode 100644 index 00000000..0fb9b18b --- /dev/null +++ b/packages/webamp-modern-2/src/skin/Status.ts @@ -0,0 +1,16 @@ +import GuiObj from "./GuiObj"; + +// Maybe this? +// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3CWasabi:StandardFrame:Status.2F.3E +export default class Status extends GuiObj { + setXmlAttr(key: string, value: string): boolean { + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + default: + return false; + } + return true; + } +} diff --git a/packages/webamp-modern-2/src/skin/SystemObject.ts b/packages/webamp-modern-2/src/skin/SystemObject.ts index c03e8d99..5162b700 100644 --- a/packages/webamp-modern-2/src/skin/SystemObject.ts +++ b/packages/webamp-modern-2/src/skin/SystemObject.ts @@ -1,18 +1,30 @@ import { interpret } from "../maki/interpreter"; import { getClass } from "../maki/objects"; import { ParsedMaki } from "../maki/parser"; +import { SkinContext } from "../types"; +import Button from "./Button"; +import Container from "./Container"; import Group from "./Group"; +import Layer from "./Layer"; +import Layout from "./Layout"; +import PopupMenu from "./PopupMenu"; +import Status from "./Status"; +import Text from "./Text"; +import ToggleButton from "./ToggleButton"; export default class SystemObject { _parentGroup: Group; _parsedScript: ParsedMaki; + _context: SkinContext; constructor(parsedScript: ParsedMaki) { this._parsedScript = parsedScript; } - init() { + init(context: SkinContext) { + this._context = context; + // dumpScriptDebug(this._parsedScript); const initialVariable = this._parsedScript.variables[0]; if (initialVariable.type !== "OBJECT") { throw new Error("First variable was not SystemObject."); @@ -21,6 +33,12 @@ export default class SystemObject { // TODO: How should we setup bindings? // console.log(this._parsedScript.bindings); interpret(0, this._parsedScript, classResover); + + interpret( + this._parsedScript.bindings[0].commandOffset, + this._parsedScript, + classResover + ); } setParentGroup(group: Group) { @@ -35,16 +53,153 @@ export default class SystemObject { getskinname() { return "TODO: Get the Real skin name"; } + + /** + * Read a private config entry of Integer type. Returns + * the specified default value if the section and item isn't + * found. + * + * @ret The value of the config entry. + * @param section The section from which to read the entry. + * @param item The name of the item to read. + * @param defvalue The defautl value to return if no item is found. + */ + getprivateint(section: string, item: string, defvalue: number) { + // TODO: Implement this! + // FIXME + return defvalue; + } + + /** + * Create a private config entry for your script, of Int type. + * + * @param section The section for the entry. + * @param item The item name for the entry. + * @param value The value of the entry. + */ + setprivateint(section: string, item: string, value: number) { + // FIXME + } + + /** + * Read the current time of the day. Returns a number that's + * the number of milliseconds since the start of the day (0:00). + * + * @ret The number of milliseconds since midnight. + **/ + gettimeofday(): number { + const date = new Date(); + const dateTime = date.getTime(); + const dateCopy = new Date(dateTime); + return dateTime - dateCopy.setHours(0, 0, 0, 0); + } + + /** + * @ret The requested container. + * @param container_id The containers identifier string. + **/ + getcontainer(containerId: string): Container { + const lower = containerId.toLowerCase(); + for (const container of this._context.containers) { + if (container.getId() === lower) { + return container; + } + } + throw new Error(`Could not find a container with the id; "${containerId}"`); + } + + /** + * Get the value of the left vu meter. + * Range is from 0 to 255. Linear. + * + * @ret The value of the left vu meter. + */ + getleftvumeter(): number { + return 0; + } + + /** + * Get the value of the right vu meter. + * Range is from 0 to 255. Linear. + * + * @ret The value of the right vu meter. + */ + getrightvumeter(): number { + return 0; + } + + /** + * Get the current volume. Range is from 0 to 255. + * + * @ret The current volume. + **/ + getvolume() { + return 100; + } + + /** + * Get the string representation of an integer. + * + * @ret The string equivalent of the integer. + * @param value The integer to change into a string. + */ + integertostring(value: number): string { + return String(value); + } + + /** + * Get the user's screen width in pixels. + * + * @ret The width of the user's screen. + */ + getviewportwidth() { + return window.document.documentElement.clientWidth; + } + + /** + * Get the user's screen height in pixels. + * + * @ret The height of the user's screen. + */ + getviewportheight() { + return window.document.documentElement.clientHeight; + } } function classResover(guid: string): any { switch (guid) { case "d6f50f6449b793fa66baf193983eaeef": return SystemObject; + case "e90dc47b4ae7840d0b042cb0fcf775d2": + return Container; + case "60906d4e482e537e94cc04b072568861": + return Layout; + case "5ab9fa1545579a7d5765c8aba97cc6a6": + return Layer; + case "f4787af44ef7b2bb4be7fb9c8da8bea9": + return PopupMenu; + case "698eddcd4fec8f1e44f9129b45ff09f9": + return Button; + case "b4dccfff4bcc81fe0f721b96ff0fbed5": + return ToggleButton; + case "efaa867241fa310ea985dcb74bcb5b52": + return Text; + case "0f08c9404b23af39c4b8f38059bb7e8f": + return Status; default: - console.log(); throw new Error( `Unresolvable class "${getClass(guid).name}" (guid: ${guid})` ); } } + +function dumpScriptDebug(script: ParsedMaki) { + for (const [i, binding] of script.bindings.entries()) { + const method = script.methods[binding.methodOffset]; + const guid = script.classes[method.typeOffset]; + const klass = getClass(guid); + console.log( + `${i}. ${klass.name}.${method.name} => ${binding.commandOffset}` + ); + } +} diff --git a/packages/webamp-modern-2/src/skin/Text.ts b/packages/webamp-modern-2/src/skin/Text.ts new file mode 100644 index 00000000..185e9fde --- /dev/null +++ b/packages/webamp-modern-2/src/skin/Text.ts @@ -0,0 +1,24 @@ +import GuiObj from "./GuiObj"; + +// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Ctext.2F.3E_.26_.3CWasabi:Text.2F.3E +export default class Text extends GuiObj { + setXmlAttr(key: string, value: string): boolean { + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + default: + return false; + } + return true; + } + + /* + +extern Text.setText(String txt); // changes the display/text="something" param +extern Text.setAlternateText(String txt); // overrides the display/text parameter with a custom string, set "" to cancel +extern String Text.getText(); +extern int Text.getTextWidth(); +extern Text.onTextChanged(String newtxt); + */ +} diff --git a/packages/webamp-modern-2/src/skin/ToggleButton.ts b/packages/webamp-modern-2/src/skin/ToggleButton.ts new file mode 100644 index 00000000..d8b8144b --- /dev/null +++ b/packages/webamp-modern-2/src/skin/ToggleButton.ts @@ -0,0 +1,20 @@ +import Button from "./Button"; + +// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cbutton.2F.3E_.26_.3Ctogglebutton.2F.3E +export default class ToggleButton extends Button { + setXmlAttr(key: string, value: string): boolean { + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + default: + return false; + } + return true; + } + + /* + extern ToggleButton.onToggle(Boolean onoff); + extern int TOggleButton.getCurCfgVal() + */ +} diff --git a/packages/webamp-modern-2/src/skin/XmlObj.ts b/packages/webamp-modern-2/src/skin/XmlObj.ts index 207dcaf2..40535724 100644 --- a/packages/webamp-modern-2/src/skin/XmlObj.ts +++ b/packages/webamp-modern-2/src/skin/XmlObj.ts @@ -8,4 +8,8 @@ export default class XmlObj { setXmlAttr(_key: string, _value: string): boolean { return false; } + + setxmlparam(key: string, value: string) { + this.setXmlAttr(key, value); + } } diff --git a/packages/webamp-modern-2/src/skin/parse.ts b/packages/webamp-modern-2/src/skin/parse.ts index 6be0b8e1..2b111b98 100644 --- a/packages/webamp-modern-2/src/skin/parse.ts +++ b/packages/webamp-modern-2/src/skin/parse.ts @@ -8,8 +8,13 @@ import Layout from "./Layout"; import Group from "./Group"; import Container from "./Container"; import Layer from "./Layer"; +import Slider from "./Slider"; +import Button from "./Button"; +import Text from "./Text"; +import Status from "./Status"; import { parse as parseMaki } from "../maki/parser"; import SystemObject from "./SystemObject"; +import ToggleButton from "./ToggleButton"; class ParserContext { container: Container | null = null; @@ -154,7 +159,16 @@ export default class SkinParser { "Unexpected children in XML node." ); - // TODO: Parse text + const text = new Text(); + text.setXmlAttributes(node.attributes); + const { parentGroup } = this._context; + if (parentGroup == null) { + console.warn( + `FIXME: Expected to be within a | ` + ); + return; + } + parentGroup.addChild(text); } async script(node: XmlElement) { @@ -202,7 +216,16 @@ export default class SkinParser { "Unexpected children in