Stub out gamma group

This commit is contained in:
Jordan Eldredge 2021-06-29 20:01:11 -07:00
parent 3788b9d54e
commit 9d36382fac
12 changed files with 203 additions and 25 deletions

View file

@ -5,6 +5,7 @@ import { assert, assume } from "./utils";
import BitmapFont from "./skin/BitmapFont";
import Color from "./skin/Color";
import AUDIO_PLAYER from "./skin/AudioPlayer";
import GammaGroup from "./skin/GammaGroup";
class UIRoot {
// Just a temporary place to stash things
@ -13,6 +14,7 @@ class UIRoot {
_fonts: (TrueTypeFont | BitmapFont)[] = [];
_colors: Color[] = [];
_groupDefs: XmlElement[] = [];
_gammaSets: Map<string, GammaGroup[]> = new Map();
_xuiElements: XmlElement[] = [];
addBitmap(bitmap: Bitmap) {
@ -52,13 +54,15 @@ class UIRoot {
return found;
}
getFont(id: string): TrueTypeFont | BitmapFont {
getFont(id: string): TrueTypeFont | BitmapFont | null {
const found = this._fonts.find(
(font) => font.getId().toLowerCase() === id.toLowerCase()
);
assert(found != null, `Could not find true type font with id ${id}.`);
return found;
if (found == null) {
console.warn(`Could not find true type font with id ${id}.`);
}
return found ?? null;
}
addGroupDef(groupDef: XmlElement) {
@ -77,6 +81,21 @@ class UIRoot {
return found ?? null;
}
addGammaSet(id: string, gammaSet: GammaGroup[]) {
this._gammaSets.set(id.toLowerCase(), gammaSet);
}
getGammaSet(id: string): GammaGroup[] {
const found = this._gammaSets.get(id.toLowerCase());
assume(
found != null,
`Could not find gammaset for id "${id}" from set of ${Array.from(
this._gammaSets.keys()
).join(", ")}`
);
return found;
}
getXuiElement(name: string): XmlElement | null {
const lowercaseName = name.toLowerCase();
const found = this._xuiElements.find(

View file

@ -2,6 +2,8 @@ import JSZip from "jszip";
// This module is imported early here in order to avoid a circular dependency.
import { classResolver } from "./skin/resolver";
import SkinParser from "./skin/parse";
import UI_ROOT from "./UIRoot";
import GammaGroup from "./skin/GammaGroup";
function hack() {
// Without this Snowpack will try to treeshake out resolver causing a circular
@ -27,13 +29,55 @@ async function main() {
node.appendChild(container.getDiv());
}
const gammaSet = UI_ROOT.getGammaSet("clean | orange (default)");
const div = document.createElement("div");
div.innerHTML = makeGammaSetSVG(gammaSet);
document.body.appendChild(div);
document.body.appendChild(node);
console.log("RENDER");
window.color = function (name = "clean | lightblue") {
const gammaSet = UI_ROOT.getGammaSet(name);
div.innerHTML = makeGammaSetSVG(gammaSet);
};
for (const container of parser._containers) {
container.init({ containers: parser._containers });
}
console.log("INIT");
}
function makeGammaSetSVG(gammaSet: GammaGroup[]) {
// 4000,510,-4000
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 300">
<defs>
${gammaSet.map((group) => makeGammaGroupSVG(group)).join("\n")}
</defs>
</svg>`;
}
function colorToFrac(num: string) {
// -4096 to 4096
return Number(num) / 4096;
}
// https://webplatform.github.io/docs/svg/elements/feFuncA/
// amplitude * pow(C, exponent) + offset (see below for amplitude, exponent, and offset)
// https://www.pawelporwisz.pl/winamp/wct_en.php
// TODO: Avoid XSS
function makeGammaGroupSVG(gammaGroup: GammaGroup) {
const [r, g, b] = gammaGroup._value.split(",").map(colorToFrac);
return `<filter id="${gammaGroup.getDomId()}" x="0" y="0" width="100%" height="100%">
<feComponentTransfer>
<feFuncR type="gamma" amplitude="1" exponent="1" offset="${r}" />
<feFuncG type="gamma" amplitude="1" exponent="1" offset="${g}" />
<feFuncB type="gamma" amplitude="1" exponent="1" offset="${b}" />
</feComponentTransfer>
</filter>`;
}
main();

View file

@ -564,11 +564,6 @@ class Interpreter {
// ! (not)
case 74: {
const a = this.stack.pop();
switch (a.type) {
case "STRING":
case "OBJECT":
throw new Error("Tried ! a string or object.");
}
this.push(V.newInt(!a.value));
break;
}

View file

@ -1,8 +1,10 @@
import * as Utils from "../utils";
import { assert } from "../utils";
import { assert, getId } from "../utils";
import ImageManager from "./ImageManager";
// http://wiki.winamp.com/wiki/XML_Elements#.3Cbitmap.2F.3E
export default class Bitmap {
_uniqueId: number = getId();
_id: string;
_url: string;
_img: HTMLImageElement;
@ -61,7 +63,14 @@ export default class Bitmap {
return this._height;
}
getCSSVar(): string {
return `--bitmap-${this.getId().replace(/[^a-zA-Z0-9]/g, "-")}-${
this._uniqueId
}`;
}
setUrl(url: string) {
document.documentElement.style.setProperty(this.getCSSVar(), `url(${url})`);
this._url = url;
}
@ -85,7 +94,7 @@ export default class Bitmap {
}
getBackgrondImageCSSAttribute(): string {
return `url(${this._url})`;
return `var(${this.getCSSVar()})`;
}
getBackgrondPositionCSSAttribute(): string {
@ -100,6 +109,10 @@ export default class Bitmap {
return `${width} ${height}`;
}
getBackdropFilterCSSAttribute(): string {
return `url(#${Utils.normalizeDomId(this._gammagroup)})`;
}
getCanvas(): HTMLCanvasElement {
if (this._canvas == null) {
assert(this._img != null, "Expected bitmap image to be loaded");

View file

@ -77,11 +77,15 @@ export default class Button extends GuiObj {
if (this._image != null) {
const bitmap = UI_ROOT.getBitmap(this._image);
this.setBackgroundImage(bitmap);
} else {
this.setBackgroundImage(null);
}
if (this._downimage != null) {
const downBitmap = UI_ROOT.getBitmap(this._downimage);
this.setActiveBackgroundImage(downBitmap);
} else {
this.setActiveBackgroundImage(null);
}
}

View file

@ -0,0 +1,46 @@
import { normalizeDomId, num, toBool } from "../utils";
export default class GammaGroup {
_id: string;
_value: string;
_boost: number = 0;
_gray: number = 0;
setXmlAttributes(attributes: { [attrName: string]: string }) {
for (const [key, value] of Object.entries(attributes)) {
this.setXmlAttr(key, value);
}
}
setXmlAttr(_key: string, value: string) {
const key = _key.toLowerCase();
switch (key) {
case "id":
this._id = value;
break;
case "value":
this._value = value;
break;
case "boost":
this._boost = num(value);
break;
case "gray":
this._gray = num(value);
default:
return false;
}
return true;
}
getId() {
return this._id;
}
getDomId() {
return normalizeDomId(this._id);
}
getRgb() {
return `rgb(${this._value})`;
}
}

View file

@ -117,8 +117,9 @@ export default class Group extends GuiObj {
if (this._background != null && this._drawBackground) {
const bitmap = UI_ROOT.getBitmap(this._background);
this.setBackgroundImage(bitmap);
} else {
this.setBackgroundImage(null);
}
// TODO: Clear background
}
draw() {

View file

@ -217,6 +217,22 @@ export default class GuiObj extends XmlObj {
]);
}
/**
* Hookable. Event happens when the mouse
* enters the objects area.
*/
onEnterArea() {
VM.dispatch(this, "onenterarea");
}
/**
* Hookable. Event happens when the mouse
* leaves the objects area.
*/
onLeaveArea() {
VM.dispatch(this, "onleavearea");
}
/**
* Set a target X position, in the screen, for
* the object.
@ -359,28 +375,29 @@ export default class GuiObj extends XmlObj {
this._renderHeight();
}
setBackgroundImage(bitmap: Bitmap) {
setBackgroundImage(bitmap: Bitmap | null) {
this._div.style.setProperty(
"--background-image",
bitmap.getBackgrondImageCSSAttribute()
bitmap?.getBackgrondImageCSSAttribute() ?? "none"
);
this._div.style.setProperty(
"--background-position",
bitmap.getBackgrondPositionCSSAttribute()
bitmap?.getBackgrondPositionCSSAttribute() ?? "none"
);
this._div.style.filter = bitmap?.getBackdropFilterCSSAttribute() ?? "none";
}
// JS Can't set the :active pseudo selector. Instead we have a hard-coded
// pseduo-selector in our stylesheet which references a CSS variable and then
// we control the value of that variable from JS.
setActiveBackgroundImage(bitmap: Bitmap) {
setActiveBackgroundImage(bitmap: Bitmap | null) {
this._div.style.setProperty(
"--active-background-image",
bitmap.getBackgrondImageCSSAttribute()
bitmap?.getBackgrondImageCSSAttribute() ?? "none"
);
this._div.style.setProperty(
"--active-background-position",
bitmap.getBackgrondPositionCSSAttribute()
bitmap?.getBackgrondPositionCSSAttribute() ?? "none"
);
}
@ -406,5 +423,12 @@ export default class GuiObj extends XmlObj {
this._div.addEventListener("mousedown", (e) => {
this.onLeftButtonDown(e.clientX, e.clientY);
});
this._div.addEventListener("mouseenter", (e) => {
this.onEnterArea();
});
this._div.addEventListener("mouseleave", (e) => {
this.onLeaveArea();
});
}
}

View file

@ -1,5 +1,5 @@
import UI_ROOT from "../UIRoot";
import { clamp, num, px } from "../utils";
import { assume, clamp, num, px } from "../utils";
import GuiObj from "./GuiObj";
import { VM } from "./VM";
@ -129,8 +129,6 @@ export default class Slider extends GuiObj {
const startX = rect.x;
const startY = rect.y;
const width = this.getwidth();
const startMouseX = downEvent.clientX;
const startMouseY = downEvent.clientY;
const handleMove = (moveEvent: MouseEvent) => {
const newMouseX = moveEvent.clientX;
@ -138,16 +136,23 @@ export default class Slider extends GuiObj {
const deltaX = newMouseX - startX;
const deltaY = newMouseY - startY;
// TODO: What about vertical sliders?
const xPos = clamp(deltaX, 0, width);
this._position = xPos / width;
this._renderThumbPosition();
this.onsetposition(this.getposition());
};
function handleMouseUp() {
const handleMouseUp = () => {
VM.dispatch(this, "onsetfinalposition", [
{ type: "INT", value: this.getposition() },
]);
VM.dispatch(this, "onpostedposition", [
{ type: "INT", value: this.getposition() },
]);
document.removeEventListener("mousemove", handleMove);
document.removeEventListener("mouseup", handleMouseUp);
}
};
document.addEventListener("mousemove", handleMove);
document.addEventListener("mouseup", handleMouseUp);
});
@ -156,6 +161,9 @@ export default class Slider extends GuiObj {
draw() {
super.draw();
this._div.setAttribute("data-obj-name", "Slider");
assume(this._barLeft == null, "Need to handle Slider barleft");
assume(this._barRight == null, "Need to handle Slider barright");
assume(this._barMiddle == null, "Need to handle Slider barmiddle");
this._renderThumb();
this._renderThumbPosition();
this._div.appendChild(this._thumbDiv);

View file

@ -94,7 +94,11 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
getText() {
if (this._display) {
switch (this._display) {
switch (this._display.toLowerCase()) {
case "pe_info":
return "pe_info";
case "vid_info":
return "vid_info";
case "time":
return "01:58";
case "songlength":
@ -140,6 +144,9 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
}
//
} else if (font == null) {
this._div.innerText = this.getText();
this._div.style.fontFamily = "Ariel";
} else {
throw new Error("Unexpected font");
}

View file

@ -21,6 +21,7 @@ import AnimatedLayer from "./AnimatedLayer";
import Vis from "./Vis";
import BitmapFont from "./BitmapFont";
import Color from "./Color";
import GammaGroup from "./GammaGroup";
class ParserContext {
container: Container | null = null;
@ -33,6 +34,7 @@ export default class SkinParser {
_path: string[] = [];
_context: ParserContext = new ParserContext();
_containers: Container[] = [];
_gammaSet: GammaGroup[] = [];
constructor(zip: JSZip) {
this._zip = zip;
@ -409,11 +411,19 @@ export default class SkinParser {
}
async gammaset(node: XmlElement) {
this._gammaSet = [];
await this.traverseChildren(node);
UI_ROOT.addGammaSet(node.attributes.id, this._gammaSet);
}
async gammagroup(node: XmlElement) {
await this.traverseChildren(node);
assume(
node.children.length === 0,
"Unexpected children in <gammagroup> XML node."
);
const gammaGroup = new GammaGroup();
gammaGroup.setXmlAttributes(node.attributes);
this._gammaSet.push(gammaGroup);
}
async component(node: XmlElement) {

View file

@ -31,7 +31,10 @@ export function px(size: number): string {
}
export function toBool(str: string) {
assert(str === "0" || str === "1", 'Expected bool value to be "0" or "1".');
assert(
str === "0" || str === "1",
`Expected bool value to be "0" or "1", but it was "${str}".`
);
return str === "1";
}
@ -48,3 +51,7 @@ export function ensureVmInt(num: number): number {
export function clamp(num: number, min: number, max: number): number {
return Math.max(min, Math.min(num, max));
}
export function normalizeDomId(id: string) {
return id.replace(/[^a-zA-Z0-9]/g, "-");
}