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