From 8ae08dce40b47e32412714c5d03034a7311d2776 Mon Sep 17 00:00:00 2001 From: Fathony Luthfillah Date: Sun, 20 Mar 2022 14:47:13 +0700 Subject: [PATCH] Show ColorTheme Form, Movable window/layer (#1161) * Toggle container (action) it works but the container is currently empty. * Wasabi:Frame:Xxx : parse,load,init this patch has no error, but not visible due relatW/relatH not available * implement relatW/relatH This patch shows the bigColorTheme window * movable,resizable Tested with MMD3 only * bugfix: unexpected gotoTarget result * allow unregister eventListener, etc. minor. * make the group movable * trial to move MMD3 main window * Prettier. * bugfix invalid clip-path value * speedup devtool: simplify tag,style * bugfix clip-path. Issue: the bigColorTheme didn't rendered properly * mute console * avoid lowercasing the color-theme-name * avoid double negative historically, it was easy to understand : 100% - 12px due I think there shall never be 100% + 12px. BUT they did! there is something 100% + 99px ! Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * unregisterable self-event-listener Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * improve readability Co-authored-by: Jordan Eldredge * improve readability Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * relat() -> relative() * del "../winamp modern" (specific skin only) * prettier * avoid exposing private on other class. * del dead code, +comments * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * typo Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * +comment, prettier * remove bind() for better unregister event listener * del dead code Co-authored-by: Jordan Eldredge * del dead code Co-authored-by: Jordan Eldredge * add note of what going on. Co-authored-by: Jordan Eldredge * warn for premateur bitmap that has no id. * del dead code * warn for unknown/unlisted guid * remove seekerGhost transparency hack, prettier. * typo * guarding move vs resize to be not overlapped * bugfix invalid css caused by empty array * del dead var * del premateur logic (no use case with MMD3) * add comment: clarify that getToken is 0 based. * bugfix typo of ambiguous var name. @captbaritone: having w and W in the same namespace is too confusing. * del confusing code Co-authored-by: Fathony Co-authored-by: Jordan Eldredge --- packages/webamp-modern-2/assets/MMD3.wal | Bin 1019818 -> 1019739 bytes packages/webamp-modern-2/src/UIRoot.ts | 72 +++-- packages/webamp-modern-2/src/clip_path.html | 43 +++ packages/webamp-modern-2/src/clip_path.ts | 35 +++ packages/webamp-modern-2/src/index.html | 159 +++++++++- packages/webamp-modern-2/src/index.ts | 2 +- .../webamp-modern-2/src/maki/interpreter.ts | 29 +- packages/webamp-modern-2/src/skin/Bitmap.ts | 48 +-- packages/webamp-modern-2/src/skin/Clippath.ts | 162 ++++++++++ .../src/skin/ColorThemesList.ts | 3 +- packages/webamp-modern-2/src/skin/Cursor.ts | 8 + .../webamp-modern-2/src/skin/GammaGroup.ts | 43 +-- .../src/skin/makiClasses/Button.ts | 37 ++- .../src/skin/makiClasses/Container.ts | 108 +++++-- .../src/skin/makiClasses/Group.ts | 66 ++-- .../src/skin/makiClasses/GuiObj.ts | 290 ++++++++++++------ .../src/skin/makiClasses/Layer.ts | 22 +- .../src/skin/makiClasses/Layout.ts | 101 +++++- .../src/skin/makiClasses/Movable.ts | 203 ++++++++++++ .../src/skin/makiClasses/Slider.ts | 5 +- .../src/skin/makiClasses/SystemObject.ts | 16 +- .../src/skin/makiClasses/ToggleButton.ts | 22 +- .../src/skin/makiClasses/WasabiFrame.ts | 60 ++++ packages/webamp-modern-2/src/skin/parse.ts | 77 ++++- packages/webamp-modern-2/src/utils.ts | 5 + 25 files changed, 1373 insertions(+), 243 deletions(-) create mode 100644 packages/webamp-modern-2/src/clip_path.html create mode 100644 packages/webamp-modern-2/src/clip_path.ts create mode 100644 packages/webamp-modern-2/src/skin/Clippath.ts create mode 100644 packages/webamp-modern-2/src/skin/Cursor.ts create mode 100644 packages/webamp-modern-2/src/skin/makiClasses/Movable.ts create mode 100644 packages/webamp-modern-2/src/skin/makiClasses/WasabiFrame.ts diff --git a/packages/webamp-modern-2/assets/MMD3.wal b/packages/webamp-modern-2/assets/MMD3.wal index 0194990a7ecf08214528f78fab46a020e2e29421..061d0cb3536721e6586f39f5737851a2b75b3ffa 100644 GIT binary patch delta 11184 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 delta 11096 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 = new Map(); + _gammaNames = {}; + _dummyGammaGroup: GammaGroup = null; + _activeGammaSetName: string = ""; _xuiElements: XmlElement[] = []; _activeGammaSet: GammaGroup[] | null = null; _containers: Container[] = []; @@ -38,6 +41,7 @@ export class UIRoot { this._xuiElements = []; this._activeGammaSet = null; this._containers = []; + this._gammaNames = {}; // A list of all objects created for this skin. this._objects = []; @@ -123,8 +127,15 @@ export class UIRoot { return this._containers; } + findContainer(id: string): Container { + const container = findLast(this.getContainers(), (ct) => ct.hasId(id)); + return container; + } + addGammaSet(id: string, gammaSet: GammaGroup[]) { - this._gammaSets.set(id.toLowerCase(), gammaSet); + const lower = id.toLowerCase(); + this._gammaNames[lower] = id; + this._gammaSets.set(lower, gammaSet); } enableGammaSet(id: string) { @@ -135,6 +146,7 @@ export class UIRoot { this._gammaSets.keys() ).join(", ")}` ); + this._activeGammaSetName = id; this._activeGammaSet = found; this._setCssVars(); } @@ -145,6 +157,9 @@ export class UIRoot { } _getGammaGroup(id: string): GammaGroup | null { + if (!id) { + return this._getGammaGroupDummy(); + } const lower = id.toLowerCase(); const found = findLast(this._activeGammaSet, (gammaGroup) => { return gammaGroup.getId().toLowerCase() === lower; @@ -152,25 +167,35 @@ export class UIRoot { return found ?? null; } + _getGammaGroupDummy() { + if (!this._dummyGammaGroup) { + //lazy create + this._dummyGammaGroup = new GammaGroup(); + this._dummyGammaGroup.setXmlAttributes({ + id: "dummy", + value: "0,0,0", + }); + } + return this._dummyGammaGroup; + } + _setCssVars() { - const map = new Map(); const cssRules = []; for (const bitmap of this._bitmaps) { const img = bitmap.getImg(); + if (!img) { + console.warn(`Bitmap/font ${bitmap.getId()} has no img!`); + continue; + } const groupId = bitmap.getGammaGroup(); - if (!map.has(img)) { - map.set(img, new Map()); - } - const imgCache = map.get(img); - if (!imgCache.has(groupId)) { - const gammaGroup = - groupId != null ? this._getGammaGroup(groupId) : null; - const url = - gammaGroup == null ? img.src : gammaGroup.transformImage(img); - imgCache.set(groupId, url); - } - const url = imgCache.get(groupId); - // TODO: Techincally we only need one per image/gammagroup. + const gammaGroup = this._getGammaGroup(groupId); + const url = gammaGroup.transformImage( + img, + bitmap._x, + bitmap._y, + bitmap._width, + bitmap._height + ); cssRules.push(` ${bitmap.getCSSVar()}: url(${url});`); } cssRules.unshift(":root{"); @@ -189,11 +214,7 @@ export class UIRoot { return found ?? null; } - dispatch( - action: string, - param: string | null | number, - actionTarget: string | null - ) { + dispatch(action: string, param: string | null, actionTarget: string | null) { switch (action.toLowerCase()) { case "play": this.audio.play(); @@ -213,11 +234,22 @@ export class UIRoot { case "eject": this.audio.eject(); break; + case "toggle": + this.toggleContainer(param); + break; default: assume(false, `Unknown global action: ${action}`); } } + + toggleContainer(param: string) { + const container = this.findContainer(param); + assume(container != null, `Can not toggle on unknown container: ${param}`); + container.toggle(); + } + draw() { + this._div.setAttribute("id", "ui-root"); this._div.style.imageRendering = "pixelated"; for (const container of this.getContainers()) { container.draw(); diff --git a/packages/webamp-modern-2/src/clip_path.html b/packages/webamp-modern-2/src/clip_path.html new file mode 100644 index 00000000..633bf8cd --- /dev/null +++ b/packages/webamp-modern-2/src/clip_path.html @@ -0,0 +1,43 @@ + + + + + + + Webamp Modern Test Suite + + + +
+
+ click me! +-> + + + + + + + +
+ +
+ +
+
+ + +
+
Top:
+
Right:
+
Bottom:
+
Left:
+
+ + \ No newline at end of file diff --git a/packages/webamp-modern-2/src/clip_path.ts b/packages/webamp-modern-2/src/clip_path.ts new file mode 100644 index 00000000..8bf61b9c --- /dev/null +++ b/packages/webamp-modern-2/src/clip_path.ts @@ -0,0 +1,35 @@ +import { Edges } from "./skin/Clippath"; + +document.getElementById("clickable").onclick = (ev) => { + alert("click on green!"); +}; +document.getElementById("img1").onclick = (event) => { + alert("click on IMAGE."); + event.stopPropagation(); +}; + +function main() { + const oriImg = document.getElementById("img1"); + const img2 = new Image(); + img2.onload = (ev) => { + // const canvas = document.createElement('canvas'); + const canvas = document.getElementById("canvas") as HTMLCanvasElement; + canvas.width = img2.width; + canvas.height = img2.height; + const ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + ctx.drawImage(img2, 0, 0); + const edge = new Edges(); + edge.parseCanvasTransparency(canvas); + document.getElementById("top").textContent = edge.gettop().replace(/px/gi, "").replace(/\,\s/gi, "\n"); + document.getElementById("right").textContent = edge.getright().replace(/px/gi, "").replace(/\,\s/gi, "\n"); + document.getElementById("bottom").textContent = edge.getbottom().replace(/px/gi, "").replace(/\,\s/gi, "\n"); + document.getElementById("left").textContent = edge.getleft().replace(/px/gi, "").replace(/\,\s/gi, "\n"); + // document.getElementById('app').style.clipPath = `polygon(${edge.top}, ${edge.bottom})`; + document.getElementById("app").style.clipPath = edge.getPolygon(); + }; + img2.setAttribute("src", oriImg.getAttribute("src")); +} + +main(); diff --git a/packages/webamp-modern-2/src/index.html b/packages/webamp-modern-2/src/index.html index 49744368..9f270196 100644 --- a/packages/webamp-modern-2/src/index.html +++ b/packages/webamp-modern-2/src/index.html @@ -4,30 +4,48 @@ - + Webamp Modern @@ -52,4 +193,4 @@
Work in Progress
- \ No newline at end of file + diff --git a/packages/webamp-modern-2/src/index.ts b/packages/webamp-modern-2/src/index.ts index f15d0f6b..48b600b4 100644 --- a/packages/webamp-modern-2/src/index.ts +++ b/packages/webamp-modern-2/src/index.ts @@ -22,7 +22,7 @@ function setStatus(status: string) { async function main() { setStatus("Downloading skin..."); - const skinPath = getUrlQuery(window.location, 'skin') || "assets/MMD3.wal"; + const skinPath = getUrlQuery(window.location, "skin") || "assets/MMD3.wal"; const response = await fetch(skinPath); const data = await response.blob(); await loadSkin(data); diff --git a/packages/webamp-modern-2/src/maki/interpreter.ts b/packages/webamp-modern-2/src/maki/interpreter.ts index da010012..b8bc89a3 100644 --- a/packages/webamp-modern-2/src/maki/interpreter.ts +++ b/packages/webamp-modern-2/src/maki/interpreter.ts @@ -294,12 +294,16 @@ class Interpreter { let argCount: number = klass.prototype[methodName].length; const methodDefinition = getMethod(guid, methodName); - assert( - argCount === (methodDefinition.parameters.length ?? 0), - `Arg count mismatch. Expected ${ - methodDefinition.parameters.length ?? 0 - } arguments, but found ${argCount} for ${klass.name}.${methodName}` - ); + if (methodName.toLowerCase() != "init") { + assert( + argCount === (methodDefinition.parameters.length ?? 0), + `Arg count mismatch. Expected ${ + methodDefinition.parameters.length ?? 0 + } arguments, but found ${argCount} for ${ + klass.name + }.${methodName}` + ); + } const methodArgs = []; while (argCount--) { @@ -312,7 +316,18 @@ class Interpreter { obj.value != null, `Guru Meditation: Tried to call method ${klass.name}.${methodName} on null object` ); - let value = obj.value[methodName](...methodArgs); + + // let value = obj.value[methodName](...methodArgs); + let value; + try { + value = obj.value[methodName](...methodArgs); + } catch (err) { + console.warn( + `error call: ${klass.name}.${methodName}(...${JSON.stringify(methodArgs)})`, + `err: ${err.message} obj: ${JSON.stringify(obj)}` + ); + value = null; + } if (value === undefined && returnType !== "NULL") { throw new Error( diff --git a/packages/webamp-modern-2/src/skin/Bitmap.ts b/packages/webamp-modern-2/src/skin/Bitmap.ts index 9ce453dc..1cd408b0 100644 --- a/packages/webamp-modern-2/src/skin/Bitmap.ts +++ b/packages/webamp-modern-2/src/skin/Bitmap.ts @@ -21,15 +21,12 @@ export default class Bitmap { } } - setXmlAttr(_key: string, value: string) { + setXmlAttr(_key: string, value: string): boolean { const key = _key.toLowerCase(); switch (key) { case "id": this._id = value; - this._cssVar = `--bitmap-${this.getId().replace( - /[^a-zA-Z0-9]/g, - "-" - )}-${getId()}`; + this._cssVar = `--bitmap-${this.getId().replace(/[^a-zA-Z0-9]/g, "-")}`; break; case "x": this._x = num(value) ?? 0; @@ -86,14 +83,14 @@ export default class Bitmap { "Tried to ensure a Bitmap was laoded more than once." ); + //force. also possibly set null: this._img = await imageManager.getImage(this._file); - - if (this._width == null && this._height == null) { - this.setXmlAttr("w", String(this._img.width)); - this.setXmlAttr("h", String(this._img.height)); + if (this._img) { + if (this._width == null && this._height == null) { + this.setXmlAttr("w", String(this._img.width)); + this.setXmlAttr("h", String(this._img.height)); + } } - - // this.setUrl(imgUrl); } _getBackgrondImageCSSAttribute(): string { @@ -112,26 +109,26 @@ export default class Bitmap { return `${width} ${height}`; } - _setAsBackground(div: HTMLDivElement, prefix: string) { + _setAsBackground(div: HTMLElement, prefix: string) { div.style.setProperty( `--${prefix}background-image`, this._getBackgrondImageCSSAttribute() ); - div.style.setProperty( - `--${prefix}background-position`, - this._getBackgrondPositionCSSAttribute() - ); } - setAsBackground(div: HTMLDivElement) { + setAsBackground(div: HTMLElement) { this._setAsBackground(div, ""); } - setAsActiveBackground(div: HTMLDivElement) { + setAsDownBackground(div: HTMLElement) { + this._setAsBackground(div, "down-"); + } + + setAsActiveBackground(div: HTMLElement) { this._setAsBackground(div, "active-"); } - setAsHoverBackground(div: HTMLDivElement) { + setAsHoverBackground(div: HTMLElement) { this._setAsBackground(div, "hover-"); } @@ -142,7 +139,18 @@ export default class Bitmap { this._canvas.width = this.getWidth(); this._canvas.height = this.getHeight(); const ctx = this._canvas.getContext("2d"); - ctx.drawImage(this._img, 0, 0, this.getWidth(), this.getHeight()); + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage + ctx.drawImage( + this._img, + this._x, + this._y, + this.getWidth(), + this.getHeight(), + 0, + 0, + this.getWidth(), + this.getHeight() + ); } return this._canvas; } diff --git a/packages/webamp-modern-2/src/skin/Clippath.ts b/packages/webamp-modern-2/src/skin/Clippath.ts new file mode 100644 index 00000000..a2f20230 --- /dev/null +++ b/packages/webamp-modern-2/src/skin/Clippath.ts @@ -0,0 +1,162 @@ +export class Edges { + _top: string[] = []; + _right: string[] = []; + _bottom: string[] = []; + _left: string[] = []; + + parseCanvasTransparency(canvas: HTMLCanvasElement) { + const w = canvas.width; + const h = canvas.height; + const ctx = canvas.getContext("2d"); + const data = ctx.getImageData(0, 0, w, h).data; + let points: string[] = []; + var x, y, lastX, lastY; + var lastfoundx: number, lastfoundy: number, pending: boolean; + var first: boolean; + + //? return true if not transparent + function opaque(ax: number, ay: number): boolean { + return data[(ax + ay * w) * 4 + 3] != 0; + } + + function post(ax: number, ay: number) { + points.push(`${ax}px ${ay}px`); + } + + //? top ------------------------------------------------- + points = []; + lastY = 0; + first = true; + pending = false; + for (x = 0; x < w; x++) { + //? scan top, left->right + for (y = 0; y < h; y++) { + //? find most top of non-transparent + if (opaque(x, y)) { + if (!first && y != lastY && pending) { + post(lastfoundx + 1, lastfoundy); + } + if (first || y != lastY || x == w) { + first = false; + post(x, y); + lastY = y; + pending = false; + } else if (x == w && pending) { + post(lastfoundx, lastfoundy); + } else { + pending = true; + } + lastfoundx = x; + lastfoundy = y; + break; + } + } + if (x == w - 1 && pending) { + post(lastfoundx + 1, lastfoundy); + } + } + this._top = points; // points.join(', \n') + + //? Right ------------------------------------------------- + points = []; + lastX = 0; + first = true; + pending = false; + for (y = 0; y <= h; y++) { + //? scan right, top->bottom + for (x = w - 1; x >= 0; x--) { + //? find most right of non-transparent + if (opaque(x, y)) { + if (!first && x != lastX && pending) { + post(lastfoundx + 1, lastfoundy); + } + if (first || x != lastX || y == h - 1) { + first = false; + post(x + 1, y); + lastX = x; + pending = false; + } else if (y == h && pending) { + post(lastfoundx + 1, lastfoundy); + pending = false; + } else { + pending = true; + } + lastfoundx = x; + lastfoundy = y; + break; + } + } + if (y == h - 1 && pending) { + // last + post(lastfoundx + 1, lastfoundy); + } + } + this._right = points; // points.join(', \n') + + //? bottom ------------------------------------------------- + points = []; + lastY = h - 1; + first = true; + pending = false; + for (x = w; x >= 0; x--) { + //? scan bottom, right->left + for (y = h - 1; y >= 0; y--) { + //? find most top of non-transparent + if (opaque(x, y)) { + if (!first && y != lastY && pending) { + post(lastfoundx, lastfoundy + 1); + } + if (first || y != lastY || x == 0) { + first = false; + post(x, y + 1); + lastY = y; + pending = false; + } else if (x == 0 && pending) { + post(lastfoundx, lastfoundy + 1); + pending = false; + } else { + pending = true; + } + lastfoundx = x; + lastfoundy = y; + break; + } + } + if (x == 0 && pending) { + // last + post(lastfoundx, lastfoundy + 1); + } + } + this._bottom = points; // points.join(', \n') + } + + gettop(): string { + return this._top.join(", "); + } + getright(): string { + return this._right.join(", "); + } + + getbottom(): string { + return this._bottom.join(", "); + } + + getleft(): string { + return this._left.join(", "); + } + + isSimpleRect(): boolean { + return this._top.length == 2 && this._bottom.length == 2; + } + + getPolygon(): string { + // to avoid empty between two comma separator, we explode values befor join(). + return `polygon(${[ + ...this._top, + ...this._right, + ...this._bottom, + ...this._left, + ].join(", ")})`; + // TODO: detect if first points in bottom has ben detected by right. + } +} diff --git a/packages/webamp-modern-2/src/skin/ColorThemesList.ts b/packages/webamp-modern-2/src/skin/ColorThemesList.ts index 356164c5..d6b2e338 100644 --- a/packages/webamp-modern-2/src/skin/ColorThemesList.ts +++ b/packages/webamp-modern-2/src/skin/ColorThemesList.ts @@ -29,9 +29,10 @@ export default class ColorThemesList extends GuiObj { for (const key of UI_ROOT._gammaSets.keys()) { const option = document.createElement("option"); option.value = key; - option.innerText = key; + option.innerText = UI_ROOT._gammaNames[key]; this._select.appendChild(option); } + this._select.value = UI_ROOT._activeGammaSetName; } handleAction( diff --git a/packages/webamp-modern-2/src/skin/Cursor.ts b/packages/webamp-modern-2/src/skin/Cursor.ts new file mode 100644 index 00000000..ff7c4599 --- /dev/null +++ b/packages/webamp-modern-2/src/skin/Cursor.ts @@ -0,0 +1,8 @@ +// We use a bitmask to encode the possible combinations of cursor attributes as a single number. +// https://en.wikipedia.org/wiki/Mask_(computing) +export const LEFT = 1 << 1; +export const RIGHT = 1 << 2; +export const TOP = 1 << 3; +export const BOTTOM = 1 << 4; +export const MOVE = 1 << 0 | TOP | LEFT; +export const CURSOR = 1 << 31; \ No newline at end of file diff --git a/packages/webamp-modern-2/src/skin/GammaGroup.ts b/packages/webamp-modern-2/src/skin/GammaGroup.ts index a5a6cebd..67c3f517 100644 --- a/packages/webamp-modern-2/src/skin/GammaGroup.ts +++ b/packages/webamp-modern-2/src/skin/GammaGroup.ts @@ -46,28 +46,32 @@ export default class GammaGroup { return `rgb(${this._value})`; } - // TODO: Figure out how to actually implement this. - transformImage(img: HTMLImageElement): string { - // Toggle this to play with gl transforming - if (false) { - return glTransformImage(img); - } + transformImage( + img: HTMLImageElement, + x: number, + y: number, + w: number, + h: number + ): string { const [r, g, b] = this._value.split(",").map((v) => { return Number(v) / 4096 + 1.0; }); + // because some didn't has explicit "w" attribute + const safeWidth = w || img.width; + const safeHeight = h || img.height; + // because some didn't has explicit "x" attribute + // if it is any, we threat it as background-position coordinate + const safeLeft = x ? -x : 0; + const safeTop = y ? -y : 0; const canvas = document.createElement("canvas"); - canvas.width = img.width; - canvas.height = img.height; + canvas.width = safeWidth; + canvas.height = safeHeight; const ctx = canvas.getContext("2d"); - ctx.drawImage(img, 0, 0); - const imageData = ctx.getImageData(0, 0, img.width, img.height); + // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage + ctx.drawImage(img, safeLeft, safeTop); + const imageData = ctx.getImageData(0, 0, safeWidth, safeHeight); const data = imageData.data; for (var i = 0; i < data.length; i += 4) { - if (this._boost) { - data[i] = (data[i] >> 1, 0, 255); // red - data[i + 1] = (data[i + 1] >> 1, 0, 255); // green - data[i + 2] = (data[i + 2] >> 1, 0, 255); // blue - } let [ir, ig, ib] = [data[i], data[i + 1], data[i + 2]]; if (this._gray != 0) { if (this._gray == 2) ir = (ir + ig + ib) / 3; @@ -75,9 +79,12 @@ export default class GammaGroup { ig = ir; ib = ir; } - data[i] = clamp(ir * r, 0, 255); // red - data[i + 1] = clamp(ig * g, 0, 255); // green - data[i + 2] = clamp(ib * b, 0, 255); // blue + const mult = this._boost == 2 ? 4 : 1; + const brightness = this._boost == 1 ? 128 : this._boost == 2 ? 32 : 0; + + data[i + 0] = clamp((ir + brightness) * mult * r, 0, 255); // red + data[i + 1] = clamp((ig + brightness) * mult * g, 0, 255); // green + data[i + 2] = clamp((ib + brightness) * mult * b, 0, 255); // blue } ctx.putImageData(imageData, 0, 0); return canvas.toDataURL(); diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Button.ts b/packages/webamp-modern-2/src/skin/makiClasses/Button.ts index 8865e28f..055392de 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/Button.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/Button.ts @@ -7,6 +7,8 @@ export default class Button extends GuiObj { static GUID = "698eddcd4fec8f1e44f9129b45ff09f9"; _image: string; _downimage: string; + _hoverimage: string; + _activeimage: string; _active: boolean = false; _action: string | null = null; _param: string | null = null; @@ -39,6 +41,14 @@ export default class Button extends GuiObj { this._downimage = value; this._renderBackground(); break; + case "hoverimage": + this._hoverimage = value; + this._renderBackground(); + break; + case "activeimage": + this._activeimage = value; + this._renderBackground(); + break; case "action": this._action = value; break; @@ -86,6 +96,11 @@ export default class Button extends GuiObj { if (onoff !== this._active) { this._active = onoff; + if (this._active) { + this._div.classList.add("active"); + } else { + this._div.classList.remove("active"); + } UI_ROOT.vm.dispatch(this, "onactivate", [V.newBool(onoff)]); } } @@ -108,20 +123,36 @@ export default class Button extends GuiObj { if (this._downimage != null) { const downBitmap = UI_ROOT.getBitmap(this._downimage); - this.setActiveBackgroundImage(downBitmap); + this.setDownBackgroundImage(downBitmap); + } else { + this.setDownBackgroundImage(null); + } + + if (this._hoverimage != null) { + const hoverimage = UI_ROOT.getBitmap(this._hoverimage); + this.setHoverBackgroundImage(hoverimage); + } else { + this.setHoverBackgroundImage(null); + } + + if (this._activeimage != null) { + const activeimage = UI_ROOT.getBitmap(this._activeimage); + this.setActiveBackgroundImage(activeimage); } else { this.setActiveBackgroundImage(null); } } _handleMouseDown(e: MouseEvent) { - this.setactivated(!this._active); + // don't send to parent to start move/resizing + e.stopPropagation(); + // buttonToggle will handle it } draw() { super.draw(); - this._div.setAttribute("data-obj-name", "Button"); this._div.classList.add("webamp--img"); + this._div.style.pointerEvents = "auto"; this._renderBackground(); } diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Container.ts b/packages/webamp-modern-2/src/skin/makiClasses/Container.ts index 0a39d95b..d3b26d3c 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/Container.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/Container.ts @@ -1,5 +1,5 @@ import UI_ROOT from "../../UIRoot"; -import { assert, px, removeAllChildNodes, toBool } from "../../utils"; +import { assert, num, px, removeAllChildNodes, toBool } from "../../utils"; import Layout from "./Layout"; import XmlObj from "../XmlObj"; @@ -13,9 +13,13 @@ export default class Container extends XmlObj { static GUID = "e90dc47b4ae7840d0b042cb0fcf775d2"; _layouts: Layout[] = []; _activeLayout: Layout | null = null; - _defaultVisible: boolean = true; + _visible: boolean = true; _id: string; - _div: HTMLDivElement = document.createElement("div"); + _x: number = 0; + _y: number = 0; + _componentGuid: string; // eg. "guid:{1234-...-0ABC}" + _componentAlias: string; // eg. "guid:pl" + _div: HTMLElement = document.createElement("container"); constructor() { super(); } @@ -29,8 +33,22 @@ export default class Container extends XmlObj { case "id": this._id = value.toLowerCase(); break; + case "component": + this._componentGuid = value.toLowerCase().split(":")[1]; + this.resolveAlias(); + break; case "default_visible": - this._defaultVisible = toBool(value); + this._visible = toBool(value); + break; + case "x": + case "default_x": + this._x = num(value) ?? 0; + this._renderDimensions(); + break; + case "y": + case "default_y": + this._y = num(value) ?? 0; + this._renderDimensions(); break; default: return false; @@ -44,11 +62,46 @@ export default class Container extends XmlObj { } } + resolveAlias() { + const knownContainerGuids = { + "{0000000a-000c-0010-ff7b-01014263450c}": "vis", + "{45f3f7c1-a6f3-4ee6-a15e-125e92fc3f8d}": "pl", + "{6b0edf80-c9a5-11d3-9f26-00c04f39ffc6}": "ml", + "{7383a6fb-1d01-413b-a99a-7e6f655f4591}": "con", + "{7a8b2d76-9531-43b9-91a1-ac455a7c8242}": "lir", + "{a3ef47bd-39eb-435a-9fb3-a5d87f6f17a5}": "dl", + "{f0816d7b-fffc-4343-80f2-e8199aa15cc3}": "video", + }; + const guid = this._componentGuid; + this._componentAlias = knownContainerGuids[guid]; + if (this._componentGuid && !this._componentAlias) { + console.warn( + `unknown component alias for guid:${this._componentGuid}`, + `for id:${this.getId()}` + ); + } + } + + /** + * Container sometime identified with a guid, or a-guid alias + * so which one correct (id, 'guid:{abcde}, 'guid:pl') is acceptable. + */ + hasId(id: string): boolean { + if (!id) return false; + id = id.toLowerCase(); + const useGuid = id.startsWith("guid:"); + if (useGuid) { + id = id.substring(5); + return this._componentGuid == id || this._componentAlias == id; + } else { + return this._id == id; + } + } getId() { return this._id; } - getDiv(): HTMLDivElement { + getDiv(): HTMLElement { return this._div; } @@ -66,6 +119,26 @@ export default class Container extends XmlObj { this._div.style.left = px((width - this.getWidth()) / 2); } + show() { + if (!this._activeLayout) { + this.switchToLayout(this._layouts[0]._id); + } + this._visible = true; + this._renderLayout(); + } + hide() { + this._visible = false; + this._renderLayout(); + } + toggle() { + if (!this._visible) this.show(); + else this.hide(); + } + close() { + this._activeLayout = null; + this.hide(); + } + /* Required for Maki */ /** * Get the layout associated with the an id. @@ -97,15 +170,11 @@ export default class Container extends XmlObj { removeAllChildNodes(this._div); } - setLayout(id: string) { - const layout = this.getlayout(id); - assert(layout != null, `Could not find layout with id "${id}".`); - UI_ROOT.vm.dispatch(this, "onswitchtolayout", [ - { type: "OBJECT", value: this._activeLayout }, - { type: "OBJECT", value: layout }, - ]); - this._activeLayout = layout; + switchToLayout(layout_id: string) { + const layout = this.getlayout(layout_id); + assert(layout != null, `Could not find layout with id "${layout_id}".`); this._clearCurrentLayout(); + this._activeLayout = layout; this._renderLayout(); UI_ROOT.vm.dispatch(this, "onswitchtolayout", [ { type: "OBJECT", value: layout }, @@ -119,15 +188,20 @@ export default class Container extends XmlObj { ) { switch (action) { case "SWITCH": - this.setLayout(param); + this.switchToLayout(param); break; default: UI_ROOT.dispatch(action, param, actionTarget); } } + _renderDimensions() { + this._div.style.left = px(this._x); + this._div.style.top = px(this._y); + } + _renderLayout() { - if (this._defaultVisible && this._activeLayout) { + if (this._visible && this._activeLayout) { this._activeLayout.draw(); this._div.appendChild(this._activeLayout.getDiv()); // this.center(); @@ -137,9 +211,7 @@ export default class Container extends XmlObj { } draw() { - this._div.setAttribute("data-xml-id", this.getId()); - this._div.setAttribute("data-obj-name", "Container"); - this._div.style.position = "absolute"; + this._div.setAttribute("id", this.getId()); this._renderLayout(); } } diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Group.ts b/packages/webamp-modern-2/src/skin/makiClasses/Group.ts index 682e580a..e4500eaf 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/Group.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/Group.ts @@ -2,19 +2,17 @@ import * as Utils from "../../utils"; import UI_ROOT from "../../UIRoot"; import GuiObj from "./GuiObj"; import SystemObject from "./SystemObject"; +import Movable from "./Movable"; // http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cgroup.2F.3E -export default class Group extends GuiObj { +export default class Group extends Movable { static GUID = "45be95e5419120725fbb5c93fd17f1f9"; _parent: Group; _instanceId: string; _background: string; _desktopAlpha: boolean; _drawBackground: boolean = true; - _minimumHeight: number; - _maximumHeight: number; - _minimumWidth: number; - _maximumWidth: number; + _isLayout: boolean = false; _systemObjects: SystemObject[] = []; _children: GuiObj[] = []; @@ -35,18 +33,6 @@ export default class Group extends GuiObj { this._drawBackground = Utils.toBool(value); this._renderBackground(); break; - case "minimum_h": - this._minimumHeight = Utils.num(value); - break; - case "minimum_w": - this._minimumWidth = Utils.num(value); - break; - case "maximum_h": - this._maximumHeight = Utils.num(value); - break; - case "maximum_w": - this._maximumWidth = Utils.num(value); - break; default: return false; } @@ -106,28 +92,38 @@ export default class Group extends GuiObj { ); } + getparentlayout(): Group { + let obj: Group = this; + while (obj._parent) { + if (obj._isLayout) { + break; + } + obj = obj._parent; + } + if (!obj) { + console.warn("getParentLayout", this.getId(), "failed!"); + } + return obj; + } + // This shadows `getheight()` on GuiObj getheight(): number { - if (this._height) { - return this._height; - } - if (this._background != null) { + const h = super.getheight(); + if (h == null && this._background != null) { const bitmap = UI_ROOT.getBitmap(this._background); - return bitmap.getHeight(); + if (bitmap) return bitmap.getHeight(); } - return super.getheight(); + return h; } // This shadows `getwidth()` on GuiObj getwidth(): number { - if (this._width) { - return this._width; - } - if (this._background != null) { + const w = super.getwidth(); + if (w == null && this._background != null) { const bitmap = UI_ROOT.getBitmap(this._background); - return bitmap.getWidth(); + if (bitmap) return bitmap.getWidth(); } - return super.getwidth(); + return w; } _renderBackground() { @@ -141,13 +137,17 @@ export default class Group extends GuiObj { draw() { super.draw(); - this._div.setAttribute("data-obj-name", "Group"); this._div.classList.add("webamp--img"); // It seems Groups are not responsive to click events. + if (this._movable || this._resizable) { + // this._div.style.removeProperty('pointer-events'); + this._div.style.pointerEvents = "auto"; + } else { + this._div.style.pointerEvents = "none"; + } + //TODO: allow move/resize if has ._image this._div.style.pointerEvents = "none"; - this._div.style.overflow = "hidden"; - this._div.style.height = Utils.px(this._maximumHeight); - this._div.style.width = Utils.px(this._maximumWidth); + // this._div.style.overflow = "hidden"; this._renderBackground(); for (const child of this._children) { child.draw(); diff --git a/packages/webamp-modern-2/src/skin/makiClasses/GuiObj.ts b/packages/webamp-modern-2/src/skin/makiClasses/GuiObj.ts index 74661dae..a1042634 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/GuiObj.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/GuiObj.ts @@ -1,9 +1,12 @@ import UI_ROOT from "../../UIRoot"; -import { assert, num, toBool, px, assume } from "../../utils"; +import { assert, num, toBool, px, assume, relative } from "../../utils"; import Bitmap from "../Bitmap"; import Group from "./Group"; import XmlObj from "../XmlObj"; +let BRING_LEAST: number = -1; +let BRING_MOST_TOP: number = 1; + // http://wiki.winamp.com/wiki/XML_GUI_Objects#GuiObject_.28Global_params.29 export default class GuiObj extends XmlObj { static GUID = "4ee3e1994becc636bc78cd97b028869c"; @@ -13,10 +16,22 @@ export default class GuiObj extends XmlObj { _height: number; _x: number = 0; _y: number = 0; + _minimumHeight: number = 0; + _maximumHeight: number = 0; + _minimumWidth: number = 0; + _maximumWidth: number = 0; + _relatx: string; + _relaty: string; + _relatw: string; + _relath: string; + // _resize: string; _droptarget: string; _visible: boolean = true; _alpha: number = 255; _ghost: boolean = false; + _sysregion: number = 0; + // _movable: boolean = false; + // _resizable: number = 0; _tooltip: string = ""; _targetX: number | null = null; _targetY: number | null = null; @@ -24,13 +39,106 @@ export default class GuiObj extends XmlObj { _targetHeight: number | null = null; _targetAlpha: number | null = null; _targetSpeed: number | null = null; - _div: HTMLDivElement = document.createElement("div"); + _goingToTarget: boolean = false; + _div: HTMLElement; _backgroundBitmap: Bitmap | null = null; + // _resizingEventsRegisterd: boolean = false; + // _movingEventsRegisterd: boolean = false; constructor() { super(); + this._div = document.createElement( + this.getElTag().toLowerCase().replace("_", "") + ); + } + + getElTag(): string { + return this.constructor.name; + } + + setParent(group: Group) { + this._parent = group; + } + + setXmlAttr(_key: string, value: string): boolean { + const key = _key.toLowerCase(); + switch (key) { + case "id": + this._id = value.toLowerCase(); + break; + case "w": + case "default_w": + this._width = num(value); + this._renderWidth(); + break; + case "h": + case "default_h": + this._height = num(value); + this._renderHeight(); + break; + case "x": + case "default_x": + this._x = num(value) ?? 0; + this._renderX(); + break; + case "y": + case "default_y": + this._y = num(value) ?? 0; + this._renderY(); + break; + case "minimum_h": + this._minimumHeight = num(value); + break; + case "minimum_w": + this._minimumWidth = num(value); + break; + case "maximum_h": + this._maximumHeight = num(value); + break; + case "maximum_w": + this._maximumWidth = num(value); + break; + case "relatw": + this._relatw = value; + break; + case "relath": + this._relath = value; + break; + case "relatx": + this._relatx = value; + break; + case "relaty": + this._relaty = value; + break; + case "droptarget": + this._droptarget = value; + break; + case "ghost": + this._ghost = toBool(value); + break; + case "visible": + this._visible = toBool(value); + this._renderVisibility(); + break; + case "tooltip": + this._tooltip = value; + break; + // (int) An integer [0,255] specifying the alpha blend mode of the object (0 is transparent, 255 is opaque). Default is 255. + case "alpha": + this._alpha = num(value); + case "sysregion": + this._sysregion = num(value); + break; + default: + return false; + } + return true; + } + + init() { this._div.addEventListener("mousedown", (e) => { + e.stopPropagation(); /* if (this._backgroundBitmap != null) { const { clientX, clientY } = e; @@ -58,6 +166,7 @@ export default class GuiObj extends XmlObj { this.onLeftButtonDown(e.clientX, e.clientY); const mouseUpHandler = (e) => { + // e.stopPropagation(); this.onLeftButtonUp(e.clientX, e.clientY); this._div.removeEventListener("mouseup", mouseUpHandler); }; @@ -72,59 +181,7 @@ export default class GuiObj extends XmlObj { }); } - setParent(group: Group) { - this._parent = group; - } - - setXmlAttr(_key: string, value: string): boolean { - const key = _key.toLowerCase(); - switch (key) { - case "id": - this._id = value.toLowerCase(); - break; - case "w": - this._width = num(value); - this._renderWidth(); - break; - case "h": - this._height = num(value); - this._renderHeight(); - break; - case "x": - this._x = num(value) ?? 0; - this._renderX(); - break; - case "y": - this._y = num(value) ?? 0; - this._renderY(); - break; - case "droptarget": - this._droptarget = value; - break; - case "ghost": - this._ghost = toBool(value); - break; - case "visible": - this._visible = toBool(value); - this._renderVisibility(); - break; - case "tooltip": - this._tooltip = value; - break; - // (int) An integer [0,255] specifying the alpha blend mode of the object (0 is transparent, 255 is opaque). Default is 255. - case "alpha": - this._alpha = num(value); - default: - return false; - } - return true; - } - - init() { - // pass - } - - getDiv(): HTMLDivElement { + getDiv(): HTMLElement { return this._div; } @@ -155,7 +212,7 @@ export default class GuiObj extends XmlObj { * @ret The top edge's position (in screen coordinates). */ gettop(): number { - return this._div.getBoundingClientRect().y; + return this._y; } /** @@ -165,7 +222,7 @@ export default class GuiObj extends XmlObj { * @ret The left edge's position (in screen coordinates). */ getleft(): number { - return this._div.getBoundingClientRect().x; + return this._x; } /** @@ -174,14 +231,12 @@ export default class GuiObj extends XmlObj { * @ret The height of the object. */ getheight(): number { - /* - assert( - this._height != null, - `Expected GUIObj to have a height in ${this.getId()}.` - ); - */ - // FIXME - return this._height ?? 0; + if (this._height || this._minimumHeight || this._maximumHeight) { + let h = Math.max(this._height || 0, this._minimumHeight); + h = Math.min(h, this._maximumHeight || h); + return h; + } + return this._height; } /** @@ -190,13 +245,14 @@ export default class GuiObj extends XmlObj { * @ret The width of the object. */ getwidth(): number { - /* - assert( - this._width != null, - `Expected GUIObj to have a width in ${this.getId()}.` - ); - */ - return this._width ?? 0; + if (this._width || this._minimumWidth || this._maximumWidth) { + let w = Math.max(this._width || 0, this._minimumWidth); + if (this._maximumHeight) { + w = Math.min(w, this._maximumWidth || w); + } + return w; + } + return this._width; } /** @@ -358,6 +414,7 @@ export default class GuiObj extends XmlObj { * Begin transition to previously set target. */ gototarget() { + this._goingToTarget = true; const duration = this._targetSpeed * 1000; const startTime = performance.now(); @@ -370,30 +427,47 @@ export default class GuiObj extends XmlObj { ]; const changes: { - [key: string]: { start: number; delta: number; renderKey: string }; + [key: string]: { + start: number; + delta: number; + renderKey: string; + target: number; + positive: boolean; + }; } = {}; for (const [key, targetKey, renderKey] of pairs) { const target = this[targetKey]; if (target != null) { const start = this[key]; + const positive = target > start; const delta = target - start; - changes[key] = { start, delta, renderKey }; + changes[key] = { start, delta, renderKey, target, positive }; } } + const clamp = (current, target, positive) => { + if (positive) { + return Math.min(current, target); + } else { + return Math.max(current, target); + } + }; + const update = (time: number) => { const timeDiff = time - startTime; const progress = timeDiff / duration; - for (const [key, { start, delta, renderKey }] of Object.entries( - changes - )) { - this[key] = start + delta * progress; + for (const [ + key, + { start, delta, renderKey, target, positive }, + ] of Object.entries(changes)) { + this[key] = clamp(start + delta * progress, target, positive); this[renderKey](); } if (timeDiff < duration) { window.requestAnimationFrame(update); } else { + this._goingToTarget = false; // TODO: Clear targets? UI_ROOT.vm.dispatch(this, "ontargetreached"); } @@ -487,6 +561,22 @@ export default class GuiObj extends XmlObj { return this._alpha; } + getparentlayout(): Group { + if (this._parent) { + return this._parent.getparentlayout(); + } + } + + bringtofront() { + BRING_MOST_TOP += 1; + this._div.style.zIndex = String(BRING_MOST_TOP); + } + + bringtoback() { + BRING_LEAST -= 1; + this._div.style.zIndex = String(BRING_LEAST); + } + handleAction( action: string, param: string | null, @@ -508,10 +598,18 @@ export default class GuiObj extends XmlObj { } _renderAlpha() { - this._div.style.opacity = `${this._alpha / 255}`; + if (this._alpha != 255) { + this._div.style.opacity = `${this._alpha / 255}`; + } else { + this._div.style.removeProperty("opacity"); + } } _renderVisibility() { - this._div.style.display = this._visible ? "inline-block" : "none"; + if (!this._visible) { + this._div.style.display = "none"; + } else { + this._div.style.removeProperty("display"); + } } _renderTransate() { this._div.style.transform = `translate(${px(this._x ?? 0)}, ${px( @@ -519,16 +617,23 @@ export default class GuiObj extends XmlObj { )})`; } _renderX() { - this._div.style.left = px(this._x ?? 0); + this._div.style.left = + this._relatx == "1" ? relative(this._x ?? 0) : px(this._x ?? 0); } + _renderY() { - this._div.style.top = px(this._y ?? 0); + this._div.style.top = + this._relaty == "1" ? relative(this._y ?? 0) : px(this._y ?? 0); } + _renderWidth() { - this._div.style.width = px(this.getwidth()); + this._div.style.width = + this._relatw == "1" ? relative(this._width ?? 0) : px(this.getwidth()); } + _renderHeight() { - this._div.style.height = px(this.getheight()); + this._div.style.height = + this._relath == "1" ? relative(this._height ?? 0) : px(this.getheight()); } _renderDimensions() { @@ -544,13 +649,24 @@ export default class GuiObj extends XmlObj { bitmap.setAsBackground(this._div); } else { this._div.style.setProperty(`--background-image`, "none"); - this._div.style.setProperty(`--background-position`, "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. + setDownBackgroundImage(bitmap: Bitmap | null) { + if (bitmap != null) { + bitmap.setAsDownBackground(this._div); + } + } + + setHoverBackgroundImage(bitmap: Bitmap | null) { + if (bitmap != null) { + bitmap.setAsHoverBackground(this._div); + } + } + setActiveBackgroundImage(bitmap: Bitmap | null) { if (bitmap != null) { bitmap.setAsActiveBackground(this._div); @@ -558,10 +674,8 @@ export default class GuiObj extends XmlObj { } draw() { - this._div.setAttribute("data-id", this.getId()); - this._div.setAttribute("data-obj-name", "GuiObj"); + this.getId() && this._div.setAttribute("id", this.getId()); this._renderVisibility(); - this._div.style.position = "absolute"; this._renderAlpha(); if (this._tooltip) { this._div.setAttribute("title", this._tooltip); diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Layer.ts b/packages/webamp-modern-2/src/skin/makiClasses/Layer.ts index 9b1ea842..88503d8d 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/Layer.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/Layer.ts @@ -1,19 +1,25 @@ import GuiObj from "./GuiObj"; import UI_ROOT from "../../UIRoot"; +import Movable from "./Movable"; +import { Edges } from "../Clippath"; // http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Clayer.2F.3E -export default class Layer extends GuiObj { +export default class Layer extends Movable { static GUID = "5ab9fa1545579a7d5765c8aba97cc6a6"; _image: string; setXmlAttr(key: string, value: string): boolean { if (super.setXmlAttr(key, value)) { + if (key == "sysregion") { + this._renderRegion(); + } return true; } switch (key) { case "image": this._image = value; this._renderBackground(); + this._renderRegion(); break; default: return false; @@ -50,9 +56,21 @@ export default class Layer extends GuiObj { this.setBackgroundImage(bitmap); } + _renderRegion() { + if (this._sysregion == 1 && this._image) { + const canvas = UI_ROOT.getBitmap(this._image).getCanvas(); + const edge = new Edges(); + edge.parseCanvasTransparency(canvas); + if (edge.isSimpleRect()) { + this.setXmlAttr("sysregion", "0"); + } else { + this._div.style.clipPath = edge.getPolygon(); + } + } + } + draw() { super.draw(); - this._div.setAttribute("data-obj-name", "Layer"); this._div.classList.add("webamp--img"); this._renderBackground(); } diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Layout.ts b/packages/webamp-modern-2/src/skin/makiClasses/Layout.ts index 753e3ffb..da6aed05 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/Layout.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/Layout.ts @@ -1,6 +1,8 @@ import Group from "./Group"; import * as Utils from "../../utils"; import Container from "./Container"; +import { LEFT, RIGHT, TOP, BOTTOM, CURSOR, MOVE } from "../Cursor"; +import { px } from "../../utils"; // > A layout is a special kind of group, which shown inside a container. Each // > layout represents an appearance for that window. Layouts give you the ability @@ -13,6 +15,17 @@ import Container from "./Container"; export default class Layout extends Group { static GUID = "60906d4e482e537e94cc04b072568861"; _parentContainer: Container | null = null; + _resizingDiv: HTMLDivElement = null; + _resizing: boolean = false; + _resizable: number = 0; // combination of 4 directions: N/E/W/S + _movingStartX: number; //container XY + _movingStartY: number; + _moving: boolean = false; + + constructor() { + super(); + this._isLayout = true; + } setXmlAttr(key: string, value: string): boolean { if (super.setXmlAttr(key, value)) { @@ -53,8 +66,90 @@ export default class Layout extends Group { } } - draw() { - super.draw(); - this._div.setAttribute("data-obj-name", "Layout"); + setResizing(cmd: string, dx: number, dy: number) { + const clampW = (w): number => { + w = this._maximumWidth ? Math.min(w, this._maximumWidth) : w; + w = this._minimumWidth ? Math.max(w, this._minimumWidth) : w; + return w; + }; + const clampH = (h): number => { + h = this._maximumHeight ? Math.min(h, this._maximumHeight) : h; + h = this._minimumHeight ? Math.max(h, this._minimumHeight) : h; + return h; + }; + const r = this._div.getBoundingClientRect(); + if (cmd == "constraint") { + this._resizable = dx; + } else if (cmd == "start") { + this.bringtofront(); + this._resizing = true; + this._resizingDiv = document.createElement("div"); + this._resizingDiv.className = "resizing"; + this._resizingDiv.style.cssText = "position:absolute; top:0; left:0;"; + this._resizingDiv.style.width = px(r.width); + this._resizingDiv.style.height = px(r.height); + this._div.appendChild(this._resizingDiv); + } else if (dx == CURSOR && dy == CURSOR) { + this._resizingDiv.style.cursor = cmd; + } else if (cmd == "move") { + if (!this._resizing) { + return; + } + // console.log(`resizing dx:${dx} dy:${dy}`); + if (this._resizable & RIGHT) + this._resizingDiv.style.width = px(clampW(r.width + dx)); + if (this._resizable & BOTTOM) + this._resizingDiv.style.height = px(clampH(r.height + dy)); + if (this._resizable & LEFT) { + this._resizingDiv.style.left = px(dx); + this._resizingDiv.style.width = px(clampW(r.width + -dx)); + } + if (this._resizable & TOP) { + this._resizingDiv.style.top = px(dy); + this._resizingDiv.style.height = px(clampH(r.height + -dy)); + } + } else if (cmd == "final") { + if (!this._resizing) { + return; + } + this._resizing = false; + this.setXmlAttr("w", this._resizingDiv.offsetWidth.toString()); + this.setXmlAttr("h", this._resizingDiv.offsetHeight.toString()); + const container = this._parentContainer; + container.setXmlAttr( + "x", + (container._x + this._resizingDiv.offsetLeft).toString() + ); + container.setXmlAttr( + "y", + (container._y + this._resizingDiv.offsetTop).toString() + ); + this._resizingDiv.remove(); + this._resizingDiv = null; + } + } + + // MOVING THINGS ===================== + setMoving(cmd: string, dx: number, dy: number) { + const container = this._parentContainer; + if (cmd == "start") { + this._moving = true; + this._movingStartX = container._x; + this._movingStartY = container._y; + this.bringtofront(); + } else if (dx == CURSOR && dy == CURSOR) { + } else if (cmd == "move") { + if (!this._moving) { + return; + } + // console.log(`moving dx:${dx} dy:${dy}`); + container.setXmlAttr("x", (this._movingStartX + dx).toString()); + container.setXmlAttr("y", (this._movingStartY + dy).toString()); + } else if (cmd == "final") { + if (!this._moving) { + return; + } + this._moving = false; + } } } diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Movable.ts b/packages/webamp-modern-2/src/skin/makiClasses/Movable.ts new file mode 100644 index 00000000..cda2a4a1 --- /dev/null +++ b/packages/webamp-modern-2/src/skin/makiClasses/Movable.ts @@ -0,0 +1,203 @@ +/** + * this file is needed to workaround of button-moving-layout issue + */ + +import { toBool } from "../../utils"; +import GuiObj from "./GuiObj"; + +import Layout from "./Layout"; +import { LEFT, RIGHT, TOP, BOTTOM, CURSOR, MOVE } from "../Cursor"; + +export default class Movable extends GuiObj { + _movable: boolean = false; + _resizable: number = 0; + _resize: string; + _resizingEventsRegistered: boolean = false; + _movingEventsRegistered: boolean = false; + + setXmlAttr(_key: string, value: string): boolean { + const key = _key.toLowerCase(); + if (super.setXmlAttr(key, value)) { + return true; + } + switch (key) { + case "move": + this._movable = toBool(value); + this._renderCssCursor(); + break; + case "resize": + this._resize = value == "0" ? "" : value; + this._renderCssCursor(); + break; + + default: + return false; + } + return true; + } + + _renderCssCursor() { + // only one of this possible: movable or resizable. not both + if (this._movable) { + this._unregisterResizingEvents(); + // winamp cursor for movable area is default/arrow. + this._div.style.removeProperty("cursor"); + this._resizable = MOVE; // = left + top - (width, height) + this._registerMovingEvents(); + } else { + this._unregisterMovingEvents(); + + switch (this._resize) { + case "right": + this._div.style.cursor = "e-resize"; + this._resizable = RIGHT; + break; + case "left": + this._div.style.cursor = "w-resize"; + this._resizable = LEFT; + break; + case "top": + this._div.style.cursor = "n-resize"; + this._resizable = TOP; + break; + case "bottom": + this._div.style.cursor = "s-resize"; + this._resizable = BOTTOM; + break; + case "topleft": + this._div.style.cursor = "nw-resize"; + this._resizable = TOP | LEFT; + break; + case "topright": + this._div.style.cursor = "ne-resize"; + this._resizable = TOP | RIGHT; + break; + case "bottomleft": + this._div.style.cursor = "sw-resize"; + this._resizable = BOTTOM | LEFT; + break; + case "bottomright": + this._div.style.cursor = "se-resize"; + this._resizable = BOTTOM | RIGHT; + break; + default: + this._div.style.removeProperty("cursor"); + this._resizable = 0; + } + + if (this._resizable != 0) { + this._registerResizingEvents(); + } else { + this._unregisterResizingEvents(); + } + } + } + + _registerResizingEvents() { + if (this._resizingEventsRegistered) { + return; + } + this._resizingEventsRegistered = true; + this._div.addEventListener("mousedown", this._handleResizing); + } + + _unregisterResizingEvents() { + if (this._resizingEventsRegistered) { + this._div.removeEventListener("mousedown", this._handleResizing); + this._resizingEventsRegistered = false; + } + } + + _handleResizing = (downEvent: MouseEvent) => { + downEvent.stopPropagation(); + if (downEvent.button != 0) return; // only care LeftButton + const layout = this.getparentlayout() as Layout; + layout.setResizing("constraint", this._resizable, 0); + layout.setResizing("start", 0, 0); + layout.setResizing( + this._div.style.getPropertyValue("cursor"), + CURSOR, + CURSOR + ); + const startX = downEvent.clientX; + const startY = downEvent.clientY; + + const handleMove = (moveEvent: MouseEvent) => { + const newMouseX = moveEvent.clientX; + const newMouseY = moveEvent.clientY; + const deltaY = newMouseY - startY; + const deltaX = newMouseX - startX; + layout.setResizing("move", deltaX, deltaY); + }; + + const handleMouseUp = (upEvent: MouseEvent) => { + upEvent.stopPropagation(); + if (upEvent.button != 0) return; // only care LeftButton + document.removeEventListener("mousemove", handleMove); + document.removeEventListener("mouseup", handleMouseUp); + const newMouseX = upEvent.clientX; + const newMouseY = upEvent.clientY; + const deltaY = newMouseY - startY; + const deltaX = newMouseX - startX; + layout.setResizing("final", deltaX, deltaY); + }; + document.addEventListener("mousemove", handleMove); + document.addEventListener("mouseup", handleMouseUp); + }; + + _registerMovingEvents() { + if (this._movingEventsRegistered) { + return; + } + this._movingEventsRegistered = true; + this._div.addEventListener("mousedown", this._handleMoving); + } + + _unregisterMovingEvents() { + if (this._movingEventsRegistered) { + this._div.removeEventListener("mousedown", this._handleMoving); + this._movingEventsRegistered = false; + } + } + + _handleMoving = (downEvent: MouseEvent) => { + downEvent.stopPropagation(); + if (downEvent.button != 0) return; // only care LeftButton + const layout = this.getparentlayout() as Layout; + layout.setMoving("start", 0, 0); + const startX = downEvent.clientX; + const startY = downEvent.clientY; + + const handleMove = (moveEvent: MouseEvent) => { + const newMouseX = moveEvent.clientX; + const newMouseY = moveEvent.clientY; + const deltaY = newMouseY - startY; + const deltaX = newMouseX - startX; + layout.setMoving("move", deltaX, deltaY); + }; + + const handleMouseUp = (upEvent: MouseEvent) => { + if (upEvent.button != 0) return; // only care LeftButton + upEvent.stopPropagation(); + document.removeEventListener("mousemove", handleMove); + document.removeEventListener("mouseup", handleMouseUp); + const newMouseX = upEvent.clientX; + const newMouseY = upEvent.clientY; + const deltaY = newMouseY - startY; + const deltaX = newMouseX - startX; + layout.setMoving("final", deltaX, deltaY); + }; + document.addEventListener("mousemove", handleMove); + document.addEventListener("mouseup", handleMouseUp); + }; + + draw() { + super.draw(); + if (this._movable || this._resizable) { + this._div.style.pointerEvents = "auto"; + } else if (this._ghost) { + this._div.style.pointerEvents = "none"; + this._div.style.setProperty("--pointer-events-by", "movable"); + } + } +} diff --git a/packages/webamp-modern-2/src/skin/makiClasses/Slider.ts b/packages/webamp-modern-2/src/skin/makiClasses/Slider.ts index 8a788683..1a6a5fcf 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/Slider.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/Slider.ts @@ -30,6 +30,7 @@ export default class Slider extends GuiObj { constructor() { super(); this._thumbDiv.addEventListener("mousedown", (downEvent: MouseEvent) => { + downEvent.stopPropagation(); const bitmap = UI_ROOT.getBitmap(this._thumb); const startX = downEvent.clientX; const startY = downEvent.clientY; @@ -38,6 +39,7 @@ export default class Slider extends GuiObj { const initialPostition = this._position; const handleMove = (moveEvent: MouseEvent) => { + moveEvent.stopPropagation(); const newMouseX = moveEvent.clientX; const newMouseY = moveEvent.clientY; const deltaY = newMouseY - startY; @@ -53,7 +55,8 @@ export default class Slider extends GuiObj { this.onsetposition(this.getposition()); }; - const handleMouseUp = () => { + const handleMouseUp = (upEvent: MouseEvent) => { + upEvent.stopPropagation(); UI_ROOT.vm.dispatch(this, "onsetfinalposition", [ { type: "INT", value: this.getposition() }, ]); diff --git a/packages/webamp-modern-2/src/skin/makiClasses/SystemObject.ts b/packages/webamp-modern-2/src/skin/makiClasses/SystemObject.ts index 9151041d..c9f1aaf7 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/SystemObject.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/SystemObject.ts @@ -20,10 +20,12 @@ export default class SystemObject extends BaseObject { static GUID = "d6f50f6449b793fa66baf193983eaeef"; _parentGroup: Group; _parsedScript: ParsedMaki; + _param: string; - constructor(parsedScript: ParsedMaki) { + constructor(parsedScript: ParsedMaki, param: string) { super(); this._parsedScript = parsedScript; + this._param = param; UI_ROOT.audio.onSeek(() => { UI_ROOT.vm.dispatch(this, "onseek", [ { type: "INT", value: UI_ROOT.audio.getCurrentTimePercent() * 255 }, @@ -481,8 +483,8 @@ export default class SystemObject extends BaseObject { * * @ret The parameter for the script. */ - getparam() { - // TODO + getparam(): string { + return this._param; } /** @@ -543,7 +545,7 @@ export default class SystemObject extends BaseObject { * @param group_id The identifier for the group you want to create. */ newgroup(group_id: string): Group { - //TODO + return this._parentGroup.findobject(group_id) as Group; } /** @@ -1069,7 +1071,7 @@ export default class SystemObject extends BaseObject { * The index starts at 0, not 1, so be careful. * Here's a short example: * getToken("1,2,3,4,5", ",", 3); - * Would return, 3. If the token you ask for doesn't exist, an + * Would return, 4. If the token you ask for doesn't exist, an * empty string is returned. * * @ret The token requested. @@ -1078,7 +1080,9 @@ export default class SystemObject extends BaseObject { * @param tokennum The token to retreive. */ gettoken(str: string, separator: string, tokennum: number) { - // TODO + // getToken("28,39,-56,-84,0,0,1,1", ",", 3) will return "-84" + const commas = str.split(separator); + return commas[tokennum] || ""; } /** diff --git a/packages/webamp-modern-2/src/skin/makiClasses/ToggleButton.ts b/packages/webamp-modern-2/src/skin/makiClasses/ToggleButton.ts index 1525bfac..29fc940c 100644 --- a/packages/webamp-modern-2/src/skin/makiClasses/ToggleButton.ts +++ b/packages/webamp-modern-2/src/skin/makiClasses/ToggleButton.ts @@ -3,15 +3,19 @@ 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 { static GUID = "b4dccfff4bcc81fe0f721b96ff0fbed5"; - setXmlAttr(key: string, value: string): boolean { - if (super.setXmlAttr(key, value)) { - return true; - } - switch (key) { - default: - return false; - } - return true; + + getElTag(): string { + return "button"; + } + + /** + * This method is called by Button + */ + _handleMouseDown(e: MouseEvent) { + // don't send to parent to start move/resizing + e.stopPropagation(); + // implementation of standard mouse down + this.setactivated(!this._active); } draw() { diff --git a/packages/webamp-modern-2/src/skin/makiClasses/WasabiFrame.ts b/packages/webamp-modern-2/src/skin/makiClasses/WasabiFrame.ts new file mode 100644 index 00000000..78dfacb9 --- /dev/null +++ b/packages/webamp-modern-2/src/skin/makiClasses/WasabiFrame.ts @@ -0,0 +1,60 @@ +import Group from "./Group"; +import UI_ROOT from "../../UIRoot"; +import { num } from "../../utils"; + +export default class WasabiFrame extends Group { + __inited: boolean = false; + _content: string; + _shade: string; + _padtitleleft: string; + _padtitleright: string; + + getElTag(): string { + return "wasabiframe"; + } + + setXmlAttr(_key: string, value: string): boolean { + const lowerkey = _key.toLowerCase(); + // console.log('wasabi:frame.key=',lowerkey,':=', value) + if (super.setXmlAttr(lowerkey, value)) { + return true; + } + switch (lowerkey) { + case "content": + this._content = value; + break; + case "shade": + this._shade = value; + break; + case "padtitleleft": + this._padtitleleft = value; + break; + case "padtitleright": + this._padtitleright = value; + break; + default: + return false; + } + return true; + } + + init() { + // console.error('wasabi:standard->> INITing:', this._content) + if (this.__inited) return; + this.__inited = true; + + super.init(); + + for (const systemObject of this._systemObjects) { + ["content", "padtitleleft", "padtitleright", "shade"].forEach((att) => { + const myValue = this["_" + att]; + if (myValue != null) { + UI_ROOT.vm.dispatch(systemObject, "onsetxuiparam", [ + { type: "STRING", value: att }, + { type: "STRING", value: myValue }, + ]); + } + }); + } + } +} diff --git a/packages/webamp-modern-2/src/skin/parse.ts b/packages/webamp-modern-2/src/skin/parse.ts index d24828b9..714947f3 100644 --- a/packages/webamp-modern-2/src/skin/parse.ts +++ b/packages/webamp-modern-2/src/skin/parse.ts @@ -23,6 +23,7 @@ import Color from "./Color"; import GammaGroup from "./GammaGroup"; import ColorThemesList from "./ColorThemesList"; import { UIRoot } from "../UIRoot"; +import WasabiFrame from "./makiClasses/WasabiFrame"; class ParserContext { container: Container | null = null; @@ -142,13 +143,17 @@ export default class SkinParser { return this.colorThemesList(node); case "status": return this.status(node); - case "wasabi:standardframe:nostatus": case "wasabi:mainframe:nostatus": + case "wasabi:medialibraryframe:nostatus": + case "wasabi:playlistframe:nostatus": + case "wasabi:standardframe:nostatus": + case "wasabi:standardframe:status": + case "wasabi:visframe:nostatus": + return this.wasabiFrame(node); case "nstatesbutton": case "componentbucket": case "playlisteditor": case "wasabi:tabsheet": - case "wasabi:standardframe:status": case "snappoint": case "accelerators": case "elementalias": @@ -200,6 +205,70 @@ export default class SkinParser { this.addToGroup(group); } + async wasabiFrame(node: XmlElement) { + const frame = new WasabiFrame(); + const previousParent = this._context.parentGroup; + + //? Search Wasabi Inheritace + const xuitag: string = node.name; //Wasabi:MainFrame:NoStatus + const xuiEl: XmlElement = this._uiRoot.getXuiElement(xuitag); + if (xuiEl) { + const xuiFrame = new XmlElement("dummy", { id: xuiEl.attributes.id }); + await this.maybeApplyGroupDef(frame, xuiFrame); + } else { + const groupdef_id = this._getWasabiGroupDef(node.name); + const groupDef = this._uiRoot.getGroupDef(groupdef_id); + if (groupDef) { + await this.maybeApplyGroupDef(frame, groupDef); + // console.log('WasabiFrame success to apply groupDef.id=', groupdef_id) + } else { + // console.warn('WasabiFrame failed to apply groupDef.id=', groupdef_id) + } + } + frame.setXmlAttributes(node.attributes); + + //?content + if (node.attributes.content) { + this._context.parentGroup = frame; + await this.group( + new XmlElement("group", { + id: node.attributes.content, + w: "0", + h: "0", + relatw: "1", + relath: "1", + }) + ); + } + this._context.parentGroup = previousParent; + this.addToGroup(frame); + } + + /** taken from Winamp Modern skin */ + _getWasabiGroupDef(xmlTag: string): string { + switch (xmlTag.toLowerCase()) { + case "wasabi:mainframe:nostatus": + return "wasabi.mainframe.nostatusbar"; + case "wasabi:medialibraryframe:nostatus": + return "wasabi.medialibraryframe.nostatusbar"; + case "wasabi:playlistframe:nostatus": + return "wasabi.playlistframe.nostatusbar"; + case "wasabi:standardframe:modal": + return "wasabi.standardframe.modal"; + case "wasabi:standardframe:nostatus": + return "wasabi.standardframe.nostatusbar"; + case "wasabi:standardframe:static": + return "wasabi.standardframe.static"; + case "wasabi:standardframe:status": + return "wasabi.standardframe.statusbar"; + case "wasabi:visframe:nostatus": + return "wasabi.visframe.nostatusbar"; + default: + console.warn(`Unhandled : ${xmlTag}`); + return; + } + } + async bitmap(node: XmlElement) { assume( node.children.length === 0, @@ -248,7 +317,7 @@ export default class SkinParser { "Unexpected children in