diff --git a/packages/webamp-modern-2/src/UIRoot.ts b/packages/webamp-modern-2/src/UIRoot.ts index 419e1de8..cedaac08 100644 --- a/packages/webamp-modern-2/src/UIRoot.ts +++ b/packages/webamp-modern-2/src/UIRoot.ts @@ -1,9 +1,11 @@ import Bitmap from "./skin/Bitmap"; +import TrueTypeFont from "./skin/TrueTypeFont"; import { assert } from "./utils"; class UIRoot { // Just a temporary place to stash things _bitmaps: Bitmap[] = []; + _trueTypeFonts: TrueTypeFont[] = []; addBitmap(bitmap: Bitmap) { this._bitmaps.push(bitmap); @@ -17,6 +19,19 @@ class UIRoot { assert(found != null, `Could not find bitmap with id ${id}.`); return found; } + + addTrueTypeFont(font: TrueTypeFont) { + this._trueTypeFonts.push(font); + } + + getTrueTypeFont(id: string): TrueTypeFont { + const found = this._trueTypeFonts.find( + (font) => font._id.toLowerCase() === id.toLowerCase() + ); + + assert(found != null, `Could not find bitmap with id ${id}.`); + return found; + } } // Global Singleton for now diff --git a/packages/webamp-modern-2/src/skin/Bitmap.ts b/packages/webamp-modern-2/src/skin/Bitmap.ts index 1ef672b1..3e966f6b 100644 --- a/packages/webamp-modern-2/src/skin/Bitmap.ts +++ b/packages/webamp-modern-2/src/skin/Bitmap.ts @@ -44,6 +44,10 @@ export default class Bitmap { return true; } + getId() { + return this._id; + } + getWidth() { return this._width; } @@ -73,9 +77,19 @@ export default class Bitmap { this.setUrl(imgUrl); } - getBackgrondCSSAttribute(): string { + getBackgrondImageCSSAttribute(): string { + return `url(${this._url})`; + } + + getBackgrondPositionCSSAttribute(): string { + const x = Utils.px(-this._x); + const y = Utils.px(-this._y); + return `${x} ${y}`; + } + + getBackgrondSizeCSSAttribute(): string { const width = Utils.px(this._width); const height = Utils.px(this._height); - return `url(${this._url}) ${width} ${height}`; + return `${width} ${height}`; } } diff --git a/packages/webamp-modern-2/src/skin/Button.ts b/packages/webamp-modern-2/src/skin/Button.ts index 345d6c12..47ba52cf 100644 --- a/packages/webamp-modern-2/src/skin/Button.ts +++ b/packages/webamp-modern-2/src/skin/Button.ts @@ -1,18 +1,41 @@ import GuiObj from "./GuiObj"; +import UI_ROOT from "../UIRoot"; +import { px } from "../utils"; // http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cbutton.2F.3E_.26_.3Ctogglebutton.2F.3E export default class Button extends GuiObj { + _image: string; setXmlAttr(key: string, value: string): boolean { if (super.setXmlAttr(key, value)) { return true; } switch (key) { + case "image": + this._image = value; + break; default: return false; } return true; } + getDebugDom(): HTMLDivElement { + const div = super.getDebugDom(); + div.setAttribute("data-obj-name", "Button"); + if (this._image != null) { + const bitmap = UI_ROOT.getBitmap(this._image); + div.style.backgroundImage = bitmap.getBackgrondImageCSSAttribute(); + div.style.backgroundPosition = bitmap.getBackgrondPositionCSSAttribute(); + 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; + } + /* extern Button.onActivate(int activated); extern Button.onLeftClick(); diff --git a/packages/webamp-modern-2/src/skin/Layer.ts b/packages/webamp-modern-2/src/skin/Layer.ts index d2c2266c..2c6a024f 100644 --- a/packages/webamp-modern-2/src/skin/Layer.ts +++ b/packages/webamp-modern-2/src/skin/Layer.ts @@ -25,7 +25,8 @@ export default class Layer extends GuiObj { div.setAttribute("data-obj-name", "Layer"); if (this._image != null) { const bitmap = UI_ROOT.getBitmap(this._image); - div.style.background = bitmap.getBackgrondCSSAttribute(); + div.style.backgroundImage = bitmap.getBackgrondImageCSSAttribute(); + div.style.backgroundPosition = bitmap.getBackgrondPositionCSSAttribute(); if (div.style.width === "" && bitmap.getWidth()) { div.style.width = px(bitmap.getWidth()); } diff --git a/packages/webamp-modern-2/src/skin/Text.ts b/packages/webamp-modern-2/src/skin/Text.ts index 185e9fde..eda7631a 100644 --- a/packages/webamp-modern-2/src/skin/Text.ts +++ b/packages/webamp-modern-2/src/skin/Text.ts @@ -1,18 +1,118 @@ import GuiObj from "./GuiObj"; +import * as Utils from "../utils"; +import UI_ROOT from "../UIRoot"; // http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Ctext.2F.3E_.26_.3CWasabi:Text.2F.3E export default class Text extends GuiObj { + _display: string; + _text: string; + _bold: boolean; + _forceupcase: boolean; + _forceuppercase: boolean; + _forcelocase: boolean; + _forcelowercase: boolean; + _align: string; + _font: string; + _fontSize: number; setXmlAttr(key: string, value: string): boolean { if (super.setXmlAttr(key, value)) { return true; } switch (key) { + case "display": + // (str) Either a specific system display string or the string identifier of a text feed. Setting this value will override the text parameter. See below. + this._display = value; + break; + case "text": + // (str) A static string to be displayed. + this._text = value; + break; + case "bold": + // (str) A static string to be displayed. + this._bold = Utils.toBool(value); + break; + case "forceupcase": + // (bool) Force the system to make the display string all uppercase before display. + this._forceupcase = Utils.toBool(value); + break; + case "forceuppercase": + // (bool) Force the system to make the display string all uppercase before display. + this._forceuppercase = Utils.toBool(value); + break; + case "font": + // (id) The id of a bitmapfont or truetypefont element. If no element with that id can be found, the OS will be asked for a font with that name instead. + this._font = value; + break; + case "align": + // (str) One of the following three possible strings: "left" "center" "right" -- Default is "left." + this._align = value; + break; + case "fontsize": + // (int) The size to render the chosen font. + this._fontSize = Utils.num(value); + /* +ticker - (bool) Setting this flag causes the object to scroll left and right if the text does not fit the rectangular area of the text object. +antialias - (bool) Setting this flag causes the text to be rendered antialiased if possible. +default - (str) A parameter alias for text. +align - (str) One of the following three possible strings: "left" "center" "right" -- Default is "left." +valign - (str) One of the following three possible strings: "top" "center" "bottom" -- Default is "top." +color - (int) The comma delimited RGB color of the text. +shadowcolor - (int) The comma delimited RGB color for underrendered shadow text. +shadowx - (int) The x offset of the shadowrender. +shadowy - (int) The y offset of the shadowrender. +timeroffstyle - (int) How to display an empty timer: "0" = " : ", "1" = "00:00", and "2"="" (if one is displaying time) +timecolonwidth - (int) How many extra pixels wider or smaller should the colon be when displaying time. Default is -1. +nograb - (bool) Setting this flag will cause the text object to ignore left button down messages. Default is off. +showlen - (bool) Setting this flag will cause the text display to be appended with the length in minutes and seconds of the current song. Default is off. +forcefixed - (bool) Force the system to attempt to render the display string with fixed-width font spacing. +forcelocase - (bool) Force the system to make the display string all lowercase before display. +forcelowercase - (bool) Force the system to make the display string all lowercase before display. +bold - (bool) Render the display string in bold. +wrap - (bool) Setting this flag will cause the text to wrap in its rectangular space. Default is off. +dblclickaction - (str) A string in the form "SWITCH;layout" where layout is the id of a layout in this object's parent container. No other actions function on this object. This action is deprecated. +offsetx - (int) Extra pixels to be added to or subtracted from the calculated x value for the display string to render. +offsety - (int) Extra pixels to be added to or subtracted from the calculated x value for the display string to render. +*/ default: return false; } return true; } + getText() { + switch (this._display) { + case "time": + return "01:58"; + case "songname": + return "Niente da Caprie (3"; + case "songinfo": + return "112kbps stereo 44."; + default: + throw new Error(`Unknown text display name: "${this._display}".`); + } + return this._display ?? this._text; + } + + getDebugDom(): HTMLDivElement { + const div = super.getDebugDom(); + div.innerText = this.getText(); + if (this._bold) { + div.style.fontWeight = "bold"; + } + if (this._align) { + div.style.textAlign = this._align; + } + + if (this._font) { + const font = UI_ROOT.getTrueTypeFont(this._font); + div.style.fontFamily = font.getFontFamily(); + } + + div.style.fontSize = Utils.px(this._fontSize ?? 14); + + return div; + } + /* extern Text.setText(String txt); // changes the display/text="something" param diff --git a/packages/webamp-modern-2/src/skin/TrueTypeFont.ts b/packages/webamp-modern-2/src/skin/TrueTypeFont.ts new file mode 100644 index 00000000..4a5838eb --- /dev/null +++ b/packages/webamp-modern-2/src/skin/TrueTypeFont.ts @@ -0,0 +1,55 @@ +import * as Utils from "../utils"; +import ImageManager from "./ImageManager"; + +export default class TrueTypeFont { + _file: string; + _id: string; + _fontFace: FontFace; + + 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 "file": + this._file = value; + default: + return false; + } + return true; + } + + getId() { + return this._id; + } + + getFontFamily() { + return this._fontFace.family; + } + + dispose() { + document.fonts.delete(this._fontFace); + } + + // Ensure we've loaded the font into our asset loader. + async ensureFontLoaded(imageManager: ImageManager) { + Utils.assert( + this._fontFace == null, + "Tried to ensure a TrueTypeFont was laoded more than once." + ); + const fontUrl = await imageManager.getUrl(this._file); + + const sanitizedFile = this._file.replace(/\./, "_"); + const fontFamily = `font-${Utils.getId()}-${this.getId()}-${sanitizedFile}`; + + const font = new FontFace(fontFamily, `url(${fontUrl})`); + this._fontFace = await font.load(); + document.fonts.add(this._fontFace); + } +} diff --git a/packages/webamp-modern-2/src/skin/parse.ts b/packages/webamp-modern-2/src/skin/parse.ts index e45c43e9..f6fff73a 100644 --- a/packages/webamp-modern-2/src/skin/parse.ts +++ b/packages/webamp-modern-2/src/skin/parse.ts @@ -15,6 +15,7 @@ import Status from "./Status"; import { parse as parseMaki } from "../maki/parser"; import SystemObject from "./SystemObject"; import ToggleButton from "./ToggleButton"; +import TrueTypeFont from "./TrueTypeFont"; class ParserContext { container: Container | null = null; @@ -166,6 +167,7 @@ export default class SkinParser { node.children.length === 0, "Unexpected children in XML node." ); + console.log(node); } async text(node: XmlElement) { @@ -416,6 +418,11 @@ export default class SkinParser { node.children.length === 0, "Unexpected children in XML node." ); + const font = new TrueTypeFont(); + font.setXmlAttributes(node.attributes); + await font.ensureFontLoaded(this._imageManager); + + UI_ROOT.addTrueTypeFont(font); } async include(node: XmlElement) { diff --git a/packages/webamp-modern-2/src/utils.ts b/packages/webamp-modern-2/src/utils.ts index f0b439c3..f9d645a0 100644 --- a/packages/webamp-modern-2/src/utils.ts +++ b/packages/webamp-modern-2/src/utils.ts @@ -33,3 +33,8 @@ export function toBool(str: string) { assert(str === "0" || str === "1", 'Expected bool value to be "0" or "1".'); return str === "1"; } + +let id = 0; +export function getId(): number { + return id++; +}