mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-28 12:36:35 +00:00
Stub out gamma group
This commit is contained in:
parent
3788b9d54e
commit
9d36382fac
12 changed files with 203 additions and 25 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
46
packages/webamp-modern-2/src/skin/GammaGroup.ts
Normal file
46
packages/webamp-modern-2/src/skin/GammaGroup.ts
Normal 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})`;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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, "-");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue