From 4e833b40d5c24e6aee22ee78f61683165cac450d Mon Sep 17 00:00:00 2001 From: Jordan Eldredge Date: Mon, 21 Jun 2021 09:50:18 -0700 Subject: [PATCH] Start addign groupdef --- packages/webamp-modern-2/README.md | 24 ++++++- packages/webamp-modern-2/src/UIRoot.ts | 17 ++++- packages/webamp-modern-2/src/maki/objects.ts | 5 +- packages/webamp-modern-2/src/maki/parser.ts | 11 ++- packages/webamp-modern-2/src/skin/Group.ts | 9 +++ packages/webamp-modern-2/src/skin/GroupDef.ts | 72 +++++++++++++++++++ packages/webamp-modern-2/src/skin/parse.ts | 19 +++-- 7 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 packages/webamp-modern-2/src/skin/GroupDef.ts diff --git a/packages/webamp-modern-2/README.md b/packages/webamp-modern-2/README.md index ee60be11..30e83117 100644 --- a/packages/webamp-modern-2/README.md +++ b/packages/webamp-modern-2/README.md @@ -1,5 +1,7 @@ # TODO Next +- [ ] Figure out if global NULL is actually typed as INT in Maki. I suspect there is no NULL type, but only an INT who happens to be zero. +- [ ] GUI objects that are constructed by MAKI never get onInit called on them... - [ ] Fix all `// FIXME` - [ ] SystemObject.getruntimeversion - [ ] SystemObject.getskinname @@ -15,4 +17,24 @@ # TODO Some day - [ ] Handle case (in)sensitivity of includes. -- [ ] Handle forward/backward slashes issues (if they exist) \ No newline at end of file +- [ ] Handle forward/backward slashes issues (if they exist) + +# Phases of Initialization + +## Asset Parse + +Starting with `skin.xml`, and inlining each `` we parse XML. As we go, we initialize GUI objects and attach them to their parent. During this phase we also encounter other asset files like Maki script, images, and fonts. These are parsed as they are encountered and setaside into a look-aside table (Maki scripts might live in the tree...). + +This phase is `async` since it may require reading files from zip or doing image/font manipulation which is inherently `async`. + +## Object Initialization + +Once all look-aside tables are populated, we notify all GUI objects to initialize themselves by propogating from the root of the tree to the leaves. Each node is reponsible for notifying its children. In this phase components pull images/scripts/fonts out of their look-aside tables. [Question: Could these just be lazy?]. At this point we also hook up any event bindings/hooks that exist in Maki. + +## Maki Initialization + +Once all nodes have been initialized, we trigger/dispatch `System.onScriptLoaded` for each Maki script. + +## First paint + +Now we can begin panting diff --git a/packages/webamp-modern-2/src/UIRoot.ts b/packages/webamp-modern-2/src/UIRoot.ts index cedaac08..9d788ca4 100644 --- a/packages/webamp-modern-2/src/UIRoot.ts +++ b/packages/webamp-modern-2/src/UIRoot.ts @@ -1,4 +1,5 @@ import Bitmap from "./skin/Bitmap"; +import GroupDef from "./skin/GroupDef"; import TrueTypeFont from "./skin/TrueTypeFont"; import { assert } from "./utils"; @@ -6,6 +7,7 @@ class UIRoot { // Just a temporary place to stash things _bitmaps: Bitmap[] = []; _trueTypeFonts: TrueTypeFont[] = []; + _groupDefs: GroupDef[] = []; addBitmap(bitmap: Bitmap) { this._bitmaps.push(bitmap); @@ -26,12 +28,25 @@ class UIRoot { getTrueTypeFont(id: string): TrueTypeFont { const found = this._trueTypeFonts.find( - (font) => font._id.toLowerCase() === id.toLowerCase() + (font) => font.getId().toLowerCase() === id.toLowerCase() ); assert(found != null, `Could not find bitmap with id ${id}.`); return found; } + + addGroupDef(groupDef: GroupDef) { + this._groupDefs.push(groupDef); + } + + getGroupDef(id: string): GroupDef { + const found = this._groupDefs.find( + (def) => def.getId().toLowerCase() === id.toLowerCase() + ); + + assert(found != null, `Could not find groupdef with id ${id}.`); + return found; + } } // Global Singleton for now diff --git a/packages/webamp-modern-2/src/maki/objects.ts b/packages/webamp-modern-2/src/maki/objects.ts index bb76604e..37db4c17 100644 --- a/packages/webamp-modern-2/src/maki/objects.ts +++ b/packages/webamp-modern-2/src/maki/objects.ts @@ -43,7 +43,10 @@ export function getReturnType(classId: string, methodName: string): DataType { } } -function getMethod(classId: string, methodName: string): MethodDefinition { +export function getMethod( + classId: string, + methodName: string +): MethodDefinition { const klass = getClass(classId); return getObjectFunction(klass, methodName); } diff --git a/packages/webamp-modern-2/src/maki/parser.ts b/packages/webamp-modern-2/src/maki/parser.ts index 9ab84c12..9d6ef341 100644 --- a/packages/webamp-modern-2/src/maki/parser.ts +++ b/packages/webamp-modern-2/src/maki/parser.ts @@ -188,14 +188,20 @@ function readVariables({ makiFile, classes }) { if (variable == null) { throw new Error("Invalid type"); } + // assume(false, "Unimplemented subclass variable type"); - variables.push({ type: "OBJECT", value: object }); + variables.push({ + type: "OBJECT", + value: object, + global, + guid: variable.guid, + }); } else if (object) { const klass = classes[typeOffset]; if (klass == null) { throw new Error("Invalid type"); } - variables.push({ type: "OBJECT", value: object }); + variables.push({ type: "OBJECT", value: object, global, guid: klass }); } else { const typeName = PRIMITIVE_TYPES[typeOffset]; if (typeName == null) { @@ -225,6 +231,7 @@ function readVariables({ makiFile, classes }) { throw new Error("Invalid primitive type"); } const variable = { + global, type: typeName, value, }; diff --git a/packages/webamp-modern-2/src/skin/Group.ts b/packages/webamp-modern-2/src/skin/Group.ts index b826bd44..99dc7e22 100644 --- a/packages/webamp-modern-2/src/skin/Group.ts +++ b/packages/webamp-modern-2/src/skin/Group.ts @@ -4,7 +4,9 @@ import GuiObj from "./GuiObj"; import SystemObject from "./SystemObject"; import { SkinContext } from "../types"; +// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cgroup.2F.3E export default class Group extends GuiObj { + _instanceId: string; _background: string; _desktopAlpha: boolean; _drawBackground: boolean; @@ -20,6 +22,9 @@ export default class Group extends GuiObj { return true; } switch (key) { + case "instance_id": + this._instanceId = value; + break; case "background": this._background = value; break; @@ -53,6 +58,10 @@ export default class Group extends GuiObj { } } + getId() { + return this._instanceId || this._id; + } + addSystemObject(systemObj: SystemObject) { systemObj.setParentGroup(this); this._systemObjects.push(systemObj); diff --git a/packages/webamp-modern-2/src/skin/GroupDef.ts b/packages/webamp-modern-2/src/skin/GroupDef.ts new file mode 100644 index 00000000..b5a5c8ec --- /dev/null +++ b/packages/webamp-modern-2/src/skin/GroupDef.ts @@ -0,0 +1,72 @@ +import * as Utils from "../utils"; +import GuiObj from "./GuiObj"; + +// NOTE: This object is not represented in Maki, it's purely an XML constructa. +// http://wiki.winamp.com/wiki/XML_Containing_objects#.3Cgroupdef.3E.3C.2Fgroupdef.3E +export default class GroupDef extends GuiObj { + _background: string; + _desktopAlpha: boolean; + _drawBackground: boolean; + _minimumHeight: number; + _maximumHeight: number; + _minimumWidth: number; + _maximumWidth: number; + _children: GuiObj[] = []; + + setXmlAttr(key: string, value: string): boolean { + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + case "background": + // (str) The id of a bitmap element to be stretched as the background of the group (all of the group contents draw on top of this bitmap) + this._background = value; + break; + case "drawbackground": + // (bool) Whether or not to actually draw the background pixels (useful for using the alpha channel of a background bitmap as the drawing region for the group). + this._drawBackground = Utils.toBool(value); + break; + case "minimum_h": + // (int) The minimum height for this group, beyond which Wasabi will not allow the group to be resized. + this._minimumHeight = Utils.num(value); + break; + case "minimum_w": + // (int) The maximum width for this group, beyond which Wasabi will not allow the group to be resized. + this._minimumWidth = Utils.num(value); + break; + case "maximum_h": + // (int) The maximum height for this group, beyond which Wasabi will not allow the group to be resized. + this._maximumHeight = Utils.num(value); + break; + case "maximum_w": + // (int) The minimum width for this group, beyond which Wasabi will not allow the group to be resized. + this._maximumWidth = Utils.num(value); + break; + /* +default_w - (int) The default width for this group, if the group instantiation does not specify what the width should be. +default_h - (int) The default height for this group, if the group instantiation does not specify what the height should be. +propagatesize - (bool) Sends this group's default/minimum/maximum size parameters to the layout which contains the group. ie: if my minimum_h is 200 and my containing layout's minimum_h is 100 and I have my propagatesize flag set, my layout will now have a * minimum_h of 200, etc. +lockminmax - (bool) Locks the minimum and maximum size values to the default size value. Makes the group "nonresizable." +design_w - (int) The "design width" of this group. The "design" based sizes allow for a conditional layout of GuiObjects within them. These parameters are used in conjunction with the x1/x2/y1/y2/anchor coordinate system, which will be documented with GuiObjects. +design_h - (int) The "design height" of this group. +name - (str) A human readable string which will be the "Name" of this group, used by various objects in the system when referring the user to this groupdef. For instance, the TabSheet GuiObject contains one group for each displayed tab, and the name parameter of each group is the string displayed within each corresponding tab. +autowidthsource - (str) The id of an object contained within this group from which this group should set its width. For instance, the groupdef that defines the contents of the Checkbox GuiObject sets its width based on the size of the text object within it, which itself will be sized at instantiation depending upon the text placed within that particular text object (See: $/studio/studio/Wacs/wasabi.system/ui/guiobjects/xml/checkbox/checkbox.xml). +autoheightsource - (str) The id of an object contained within the group from which this group should set its height. +register_autopopup - (bool) This flag causes this group to be listed in the Windows submenu of the Wasabi MainMenu context menu. Its name parameter is displayed as the menu item. If that menu item is then selected, this group will be launched as a layout (in other words, in its own window). This is quite useful for the quick debugging of groupdef objects. +windowtype - (str) Specifying a groupdef as being a certain windowtype causes the group to automatically be instantiated and added to the collection specified by that windowtype. See below for more information and a list of what windowtypes currently exist in the system. +inherit_content - (bool) This parameter is used to be able to override the contents of a group already defined in the system. To go back to the "blueprints" analogy, this is like finding someone's original blueprints and editing them. The id of this groupdef should match the id of the other groupdef that you wish to modify. See below for examples and more information. +inherit_group - (str) This parameter is used to be able to copy the contents of a group already defined in the system as the starting point for one's own group definition. To go back to the "blueprints" analogy, this is like photocopying someone else's blueprints as a starting point for one's own. The param is the id of the other groupdef you wish to inherit. +inherit_params - (bool) Specifies whether or not to inherit the params of the inherited group as well as its content. Only meaningful with a valid inherit_group param or if overriding a previous groupdef. +xuitag - (str) Specifies that this group definition will create a new Xui object into the system with the given tag string. See Extending Wasabi: Creating XuiObjects for more information. +embed_xui - (str) Specifies the id of a contained object to which any XML parameters not valid for a group sent to the instantiation of the group (or the xuitag string) should be forwarded. See Extending Wasabi: Creating XuiObjects for more information. +*/ + default: + return false; + } + return true; + } + + addChild(child: GuiObj) { + this._children.push(child); + } +} diff --git a/packages/webamp-modern-2/src/skin/parse.ts b/packages/webamp-modern-2/src/skin/parse.ts index f6fff73a..afa1fca1 100644 --- a/packages/webamp-modern-2/src/skin/parse.ts +++ b/packages/webamp-modern-2/src/skin/parse.ts @@ -16,11 +16,11 @@ import { parse as parseMaki } from "../maki/parser"; import SystemObject from "./SystemObject"; import ToggleButton from "./ToggleButton"; import TrueTypeFont from "./TrueTypeFont"; +import GroupDef from "./GroupDef"; class ParserContext { container: Container | null = null; - layout: Layout | null = null; - parentGroup: Group | null = null; + parentGroup: Group | /* Group includes Layout | */ GroupDef | null = null; } export default class SkinParser { @@ -211,7 +211,14 @@ export default class SkinParser { "Expected scripts to only live within a parent group." ); - this._context.parentGroup.addSystemObject(systemObj); + // TODO: Need to investigate how scripts find their group. In corneramp, the + // script itself is not in any group. `xml/player.xml:8 + if (this._context.parentGroup instanceof Group) { + console.log("Adding script to ", this._context.parentGroup?.getId()); + this._context.parentGroup.addSystemObject(systemObj); + } else { + // Script archives can also live in but we don't know how to do that. + } } async scripts(node: XmlElement) { @@ -300,7 +307,10 @@ export default class SkinParser { } async groupdef(node: XmlElement) { - // await this.traverseChildren(node); + const groupDef = new GroupDef(); + groupDef.setXmlAttributes(node.attributes); + this._context.parentGroup = groupDef; + await this.traverseChildren(node); } async layer(node: XmlElement) { @@ -328,7 +338,6 @@ export default class SkinParser { assume(container != null, "Expected to be in a "); container.addLayout(layout); - this._context.layout = layout; this._context.parentGroup = layout; await this.traverseChildren(node); }