TrueTypeFont, Simple PlaylistEditor, scrollbars. (#1169)

* allow direct rgb as css color property

* + <wasabi:standardframe:modal:short>

* set Config, WinampConfig as global var

* normalize handleAction: del hardcoded trick

* mute console (noice reduction)

* allow internal themes in the theme list

* add (temporary) appearance to WasabiButton

* check points and add possible todo.

* yarn build, yarn serve

* Variable.guid is now optional

* + PLEdit (non gui) to hold mp3 tracks

* move eject,next,prev & <input> files to UIRoot

* trial update song-title on next/prev. (failed)

* track index correction, prettier

* show pl (GUI)

* avoid error on console log: audio interupted.

* del requirement of UI_ROOT, reg Object class.
ui_root will not be singleton in future.

* stop searching of a binding when founded.

* should never select any text.

* explicit return of a function

* avoid vscode problem (red warn on file name)

* PlayList primitive colors (bg,fg)

* plEdit: selected & currrent colors.

* +button.css, move any css of button.

* assure transparancy instead opaque

* safety when bitmap=null

* del dead code

* del dead code

* correction: studio.button instead wasabi.button

* prettier

* show scrollbar in pl (dummy)

* solve vscode/ts complain

* +common scrollbar

* always show scrollbar (full height) whatsoever.

* reposition that match. (taken from x2nie-dev)

* completing scrollbar dimensions.

* +text.shadow

* animatedlayer.onstop

* text.onchanged

* bugfix error: text-auto-wrapped.

* +some xui (drone skin)

* allow Time shown as Remaining (instead ellapsed)

* bugfix time remaining overalaped with kbps.

* comments of container types (prediction)

* bring window to most top on click.

* temporary case. (skin: drone)

* Complete Slider implementation about virtual thumb

* avoid red warning on filename of vscode.

* +dirty audio.analyzer (skin:MMD3)

* allow "vis" element to be position:absolute.

* trying skin:Warp_skin.wal (failed)

* avoid red warning on filename of vscode.

* +todo

* +api:setactivatednocallback

* pl: +slider,real. (instead of fake scroll element)

* PL slider moved when mouse wheel. (pasive)

* complete pl scroll.

* avoid red warning on filename of vscode.

* set ColorList bg

* cleanup, completing MMD3

* add Grip (pl scrollbar), cleanup.

* change where the build dir is.

Co-authored-by: Fathony <fathony@smart-leaders.net>
This commit is contained in:
Fathony Luthfillah 2022-05-08 02:11:37 +07:00 committed by GitHub
parent 73fd41f273
commit 7451c6fe64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 1295 additions and 283 deletions

View file

@ -2,5 +2,5 @@
packages/webamp/demo/built/
packages/webamp/built/
packages/webamp-modern/src/build/
packages/webamp-modern/build/
packages/webamp-modern/tools/eslint-rules/dist/

View file

@ -3,4 +3,4 @@ yarn workspace ani-cursor build
yarn workspace webamp build
yarn workspace webamp build-library
yarn workspace webamp-modern build
mv packages/webamp-modern/src/build packages/webamp/demo/built/modern
mv packages/webamp-modern/build packages/webamp/demo/built/modern

View file

@ -1 +1,2 @@
build/
build/
temp/

View file

@ -3,39 +3,43 @@
Assuming you have [Yarn](https://yarnpkg.com/) installed:
```bash
cd packages/webamp-modern-2
cd packages/webamp-modern
yarn
yarn start
```
## Performance Improvements
- [ ] We could use WebGL to try to improve the speed of switching gamma colors
- [ ] We could use some CSS techniques to avoid having to appply inline style to each BitmapFont character's DOM node.
- [ ] We could use CSS `filter` to try to improve the speed of switching gamma colors?
- [ ] We could use WebGL to try to improve the speed of switching gamma colors?
- [x] We could use some CSS techniques to avoid having to appply inline style to each BitmapFont character's DOM node.
- [ ] We should profile the parse phase to see what's taking time. Perhaps there's some sync image work that could be done lazily.
- [ ] Remove some paranoid validation in the VM.
- [ ] Consider throttling time updates coming from audio
- [ ] Attach method binding on script init.
# TODO Next
- [ ] Implement event-listner-pool for native `on('eventname')`. It will improves readability & speed
- [ ] Why doesn't scrolling work property in MMD3?
- [ ] Implement proper color
- [ ] Move gammacolor to GPU
- [x] Implement proper color
- [ ] Move gammacolor to GPU?
- [ ] Requires VM
- [ ] Look at componentbucket (Where can I find the images)
- [ ] How is the scroll window for colors supposed to work?
- [ ] How is the position slider supposed to work?
- [ ] Standardize handling of different type condition permutations in interpreter
- [ ] Implement EQ
- [x] Implement EQ
- [ ] Implament global actions
- [ ] TOGGLE
- [x] TOGGLE
- [ ] MINIMIZE
- [ ] Allow for skins which don't have gamma sets
- [x] Allow for skins which don't have gamma sets
- [ ] Figure out if global NULL is actually typed as INT in Maki. I suspect there is no NULL type, but only an INT who happens to be zero.
- [ ] Implement custom list instead of html `select`, so scrollbars can be rendered properly.
- [ ] Fix all `// FIXME`
- [ ] SystemObject.getruntimeversion
- [ ] SystemObject.getskinname
- [ ] Handle clicking through transparent: https://stackoverflow.com/questions/38487569/click-through-png-image-only-if-clicked-coordinate-is-transparent
- [x] Handle clicking through transparent: Using css `clip-path`. ~~https://stackoverflow.com/questions/38487569/click-through-png-image-only-if-clicked-coordinate-is-transparent~~
# TODO Some day

View file

@ -6,7 +6,7 @@
"scripts": {
"start": "snowpack dev",
"build": "snowpack build",
"serve": "http-server ./built",
"serve": "http-server ./build",
"lint": "yarn build-lint && eslint . --ext .js,.jsx,.ts,.tsx",
"test": "yarn jest",
"extract-object-types": "node tools/extract-object-types.js",

View file

@ -22,5 +22,6 @@ module.exports = {
},
buildOptions: {
/* ... */
out: './build'
},
};

View file

@ -19,6 +19,7 @@ import AUDIO_PLAYER, { AudioPlayer } from "./skin/AudioPlayer";
import SystemObject from "./skin/makiClasses/SystemObject";
import ComponentBucket from "./skin/makiClasses/ComponentBucket";
import GroupXFade from "./skin/makiClasses/GroupXFade";
import { PlEdit, Track } from "./skin/makiClasses/PlayList";
export class UIRoot {
_div: HTMLDivElement = document.createElement("div");
@ -26,6 +27,7 @@ export class UIRoot {
_bitmaps: Bitmap[] = [];
_fonts: (TrueTypeFont | BitmapFont)[] = [];
_colors: Color[] = [];
_dimensions: { [id: string]: number } = {}; //css: width
_groupDefs: XmlElement[] = [];
_gammaSets: Map<string, GammaGroup[]> = new Map();
_gammaNames = {};
@ -38,12 +40,25 @@ export class UIRoot {
_buckets: { [wndType: string]: ComponentBucket } = {};
_bucketEntries: { [wndType: string]: XmlElement[] } = {};
_xFades: GroupXFade[] = [];
_input: HTMLInputElement = document.createElement("input");
// A list of all objects created for this skin.
_objects: BaseObject[] = [];
//published
vm: Vm = new Vm();
audio: AudioPlayer = AUDIO_PLAYER;
playlist: PlEdit = new PlEdit();
constructor() {
//"https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Auto-Pilot_-_03_-_Seventeen.mp3";
this._input.type = "file";
this._input.setAttribute("multiple", "true");
// document.body.appendChild(this._input);
// TODO: dispose
this._input.onchange = this._inputChanged;
}
getFileAsString: (filePath: string) => Promise<string>;
getFileAsBytes: (filePath: string) => Promise<ArrayBuffer>;
getFileAsBlob: (filePath: string) => Promise<Blob>;
@ -101,6 +116,23 @@ export class UIRoot {
this._colors.push(color);
}
// to reduce polution of inline style.
addDimension(id: string, size: number) {
this._dimensions[id] = size;
}
addWidth(id: string, bitmapId: string) {
const bitmap = this.getBitmap(bitmapId);
if (bitmap) {
this.addDimension(id, bitmap.getWidth());
}
}
addHeight(id: string, bitmapId: string) {
const bitmap = this.getBitmap(bitmapId);
if (bitmap) {
this.addDimension(id, bitmap.getHeight());
}
}
getColor(id: string): Color {
const lowercaseId = id.toLowerCase();
const found = findLast(
@ -236,6 +268,7 @@ export class UIRoot {
const bitmapFonts: BitmapFont[] = this._fonts.filter(
(font) => font instanceof BitmapFont && !font.useExternalBitmap()
) as BitmapFont[];
// css of bitmaps
for (const bitmap of [...this._bitmaps, ...bitmapFonts]) {
const img = bitmap.getImg();
if (!img) {
@ -253,16 +286,21 @@ export class UIRoot {
);
cssRules.push(` ${bitmap.getCSSVar()}: url(${url});`);
}
// css of colors
for (const color of this._colors) {
const groupId = color.getGammaGroup();
const gammaGroup = this._getGammaGroup(groupId);
const url = gammaGroup.transformColor(color.getValue());
cssRules.push(` ${color.getCSSVar()}: ${url};`);
}
cssRules.unshift(":root{");
cssRules.push("}");
// css of dimensions
for (const [dimension, size] of Object.entries(this._dimensions)) {
cssRules.push(` --dim-${dimension}: ${size}px;`);
}
// cssRules.unshift(":root{");
// cssRules.push("}");
const cssEl = document.getElementById("bitmap-css");
cssEl.textContent = cssRules.join("\n");
cssEl.textContent = `:root{${cssRules.join("\n")}}`;
}
getXuiElement(name: string): XmlElement | null {
@ -281,7 +319,7 @@ export class UIRoot {
(font) => font instanceof TrueTypeFont
) as TrueTypeFont[];
for (const ttf of truetypeFonts) {
if(!ttf.hasUrl()) {
if (!ttf.hasUrl()) {
continue; // some dummy ttf (eg Arial) doesn't has url.
}
// src: url(data:font/truetype;charset=utf-8;base64,${ttf.getBase64()}) format('truetype');
@ -290,7 +328,7 @@ export class UIRoot {
src: url(${ttf.getBase64()}) format('truetype');
font-weight: normal;
font-style: normal;
}`)
}`);
}
const cssEl = document.getElementById("truetypefont-css");
cssEl.textContent = cssRules.join("\n");
@ -308,13 +346,13 @@ export class UIRoot {
this.audio.stop();
break;
case "next":
this.audio.next();
this.next();
break;
case "prev":
this.audio.previous();
this.previous();
break;
case "eject":
this.audio.eject();
this.eject();
break;
case "toggle":
this.toggleContainer(param);
@ -327,6 +365,42 @@ export class UIRoot {
}
}
next() {
const currentTrack = this.playlist.getcurrentindex();
if (currentTrack < this.playlist.getnumtracks() - 1) {
this.playlist.playtrack(currentTrack + 1);
}
this.audio.play();
//TODO: check if "repeat" is take account
}
previous() {
const currentTrack = this.playlist.getcurrentindex();
if (currentTrack > 0) {
this.playlist.playtrack(currentTrack - 1);
}
this.audio.play();
//TODO: check if "repeat" is take account
}
eject() {
// this will call _inputChanged()
this._input.click();
}
_inputChanged = () => {
this.playlist.clear();
for (var i = 0; i < this._input.files.length; i++) {
const newTrack: Track = {
filename: this._input.files[i].name,
file: this._input.files[i],
};
this.playlist.addTrack(newTrack);
}
this.audio.play();
};
toggleContainer(param: string) {
const container = this.findContainer(param);
assume(container != null, `Can not toggle on unknown container: ${param}`);
@ -390,7 +464,7 @@ export class UIRoot {
if (!filePath) return null;
const zipObj = getCaseInsensitiveFile(this._zip, filePath);
if (!zipObj) return null;
return await zipObj.async("string");
return await zipObj.async("text");
}
async getFileAsBytesZip(filePath: string): Promise<ArrayBuffer> {

View file

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Webamp Modern" />
<title>Webamp Modern</title>
<style>
:root {
--dim-button-border-top: 4px;
--dim-button-border-bottom: 4px;
--dim-button-border-left: 4px;
--dim-button-border-right: 4px;
--bitmap-wasabi-button: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAhCAYAAABX5MJvAAAAAXNSR0IArs4c6QAAArpJREFUWEfF2M1LImEcB/CvSpl16hIkXaJDlw6BBAqFIpgS9gesLKxL0KFbx0BYS+igkNRBEXzZWPTgqRJE7FKEoJbRtVey6A2MEj1Uh2b5PWxDD8Tenpk5zsH5zPf5zTPfUQPgG1Q8JEmC5h8io5ZDkiSPjIjFYjCZTOjs7IRWqxVqenl5weHhIWZnZ8EhNjY2YLVa2cU1GrKJO97e3pDP5+H1ennE7u4uzGYznYRerxcnAPD+/o7t7W24XC4Z4QWQ2tvbg8ViwevrKzo6OoQims0mQ3g8HkL8pNx/APhdKpUY4vHxkc2FyOPh4QF00zMzMzKCHtHMzs4OJiYmcH9/D4PBINKA6+tr0E3Pzc3xM1EsFmGz2XB1dSUcQdcol8uYn5/nEZubm5icnMT5+Tl6enqEJnFxcYFqtYqFhQUekc1mMTU1hdPTU3R3dwtF1Ot1hvD5fDwinU7D7Xbj5OREkeUghN/v5xHr6+uYnp5mSXR1dQlNggZzf38fi4uLPCKVSrEkzs7OhCNubm7YciwtLfGIZDLJZoKGRnQShKAkvkQ4nU7Q0Ijetm9vb1kSgUBATuIXAH8ikYDD4WAbiRIISuJLhN1uBylVRdCrnPZ1VRHj4+NoNBrqIugt+vT0pB6C6t3Y2Bien58VQwSDQf7piEQiGB0dBRUOpWZidXWVR6ytrWFkZAStVksxRDQa5RHhcBjDw8OKIuhV8dG22WYVCoUwODiIdrstPIm7uzscHBwgk8nIiO8A/iwvL2NgYIAhRPeJy8tL1Go15HI5uWN6AKTj8Tj6+vpA3wSi2/bx8TFLgorUR9tmCFL19/ezpRDdto+OjlCpVLCyssK3beqYQ0ND6O3tFd4xqWnTx9bnfYJV/q2tLRiNRpaCTqcT2qyobRcKBXzeJxhC6FX/8+PsEZUkSdX/J8j3FwQd2mpU5iWWAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-label-arrow-up: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAECAYAAABCxiV9AAAAAXNSR0IArs4c6QAAADtJREFUGFdjZEAC+vr6my5evOgHE2KEMaASvvr6+pthCsCSUAkfBgYGEP+/vr7+FpACRpgOZOOhGjYDAIf2GkT924aUAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-label-arrow-down: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAECAYAAABCxiV9AAAAAXNSR0IArs4c6QAAADtJREFUGFddi9ENABAUA5+JbhEMi0U6EamIiP61d01Ak1TiC9CTtyPkiHCfwJBUN3yE4oeBtwsfYQNnARFuGkRI3ON9AAAAAElFTkSuQmCC);
--bitmap-wasabi-button-label-arrow-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAHCAYAAAAvZezQAAAAAXNSR0IArs4c6QAAACxJREFUGFdjZEAC+vr6mxhhfBDn4sWLvmABKMeHgYGBkREmA1OJqQKrGciCAAw6FoFYQbYKAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-label-arrow-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAHCAYAAAAvZezQAAAAAXNSR0IArs4c6QAAADJJREFUGFdj1NfX33Tx4kU/BihgZGBg+K+vr78ZJggWgApuAQnCBMAaQCqxq0AxA90WAH5eIEcrvHx/AAAAAElFTkSuQmCC);
--bitmap-wasabi-button-label-ellipses: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAABCAYAAAAW/mTzAAAAAXNSR0IArs4c6QAAABdJREFUGFdj/P///38GBgYmBgaGfzAaAFPwBf6MeFJcAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-top-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAACpJREFUGFdjZGBgiGRAAoxQgWUwMbjAzJkzGYyNjRngAhs2bGCwt7dnAACwrwiMTQ+r7wAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-top: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAECAYAAAB7oZQmAAAAAXNSR0IArs4c6QAAAC1JREFUKFO1zwENAEAMwsCCy7c9I5PA8iKogUsFPMoJSNlAMxPbVUe7G+kP9Trnuwinn+5AoAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-top-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADZJREFUGFdNyLENACAIRcHHmFpRMSJTEAZhiW9iLLzyDFg8kjBAX2yrKnU3EYGkbTOjzMTdbxzN2Bxdgax5BAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAZCAYAAAAWuY87AAAAAXNSR0IArs4c6QAAANpJREFUKFNVzz1qhGAAhOF3LyEI21jZWNgICooiqCB6gIRAcpOksrEQLATBnyzBFHZit9WKeK+QD5IP24eZgbkAT8D3tm3Yts0FeAPGfd9xHEfAK/B5HMc/iMrj8cDzPJEQcL/fCYJAwrIsxHEsYZ5n0jSVME0TWZZJuN1u5HkuYRzHc2IYhvPGLyRJIirvwEff90RRdIYwDM/g+/4ZXNc9w999Mdq2LZZlyUTTNJimKaGuawzDkFBVFbquSyjLEk3TBLwAX0VRcL1eBTwDU9d1KIoiYV1XVFXlB5FfWaVhCH38AAAAAElFTkSuQmCC);
--bitmap-wasabi-button-center: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAJ5JREFUSEvt1qsOxSAMBuAhgMHefxq7BIPC4YAsEwiQ2Cn0ElzPM0z8U6cv8KWXtGVzTpJSLshgz/MQ5xxpLGyMQUIILHLfNymlsEjvHY+01mjbNmwmpRTSWmOR67rw5TrPk9Z1xWaSc8YjKaVvEPhaiTHid9cfeTPz7JNyhRDwjffe4xHnHB45jgOPGGPwyL7v+HtircV/K7VW+LfyA83KVbVMsGDdAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAZCAYAAAAWuY87AAAAAXNSR0IArs4c6QAAANlJREFUKFOt0K2qhkAQgOH17rwG28fXbEbBsGrbsGDRpkUEtbliUEwi/gert+A1yBycCYfTT314ZwZGe54Huq5juq4zADC0+74RDMN44aNd1wXDMLDv90twnieM48hM06SRaZpgnmdmWRZB3/ewriuzbZugbVsEx3EImqZB4JwT1HUN27Yx13V/4S08zyNQSmHxB97C930qqqrC4l+hLEtcKoSgK2maIgRBQBDHMUIURQRhGCIkSUIgpYR931mWZQSccziOgyml6KdCCCyKoiDI8xyWZWFSSoQf+2MCstJm+XEAAAAASUVORK5CYII=);
--bitmap-wasabi-button-bottom-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADBJREFUGFdjZGBgiGRgYFi2ceNGBmVlZQZGmMCmTZsYpKSkEAIMUMD4//9/kBY4AACmQAs4WmdEegAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-bottom: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAECAYAAAB7oZQmAAAAAXNSR0IArs4c6QAAAC5JREFUKFO1zUERAEAMArFl+kTh2a6H6uBUEAPR3cU2TdrdzEzzQECqA6Akr518JpQLp4o4YE0AAAAASUVORK5CYII=);
--bitmap-wasabi-button-bottom-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADZJREFUGFc1yLENACEMxdDPmDBSugwTJW0mMwLu3Fh6IyLobpmZgDUyk6qSuz+QhL4uAPOH8w21XhxdfKSSqwAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-hover-top-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAACpJREFUGFdjZGBgiGRAAoxQgWUwMbjAkiVLGNzd3RngAocPH2awtLRkAAC50AkQHN8nawAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-hover-top: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAECAYAAAB7oZQmAAAAAXNSR0IArs4c6QAAACtJREFUKFNjZGBgiGSgMWBkYGD4T2M7GBg/f/78n4mJiab2MP7584fmPgEAQxoJLgwvOTEAAAAASUVORK5CYII=);
--bitmap-wasabi-button-hover-top-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADVJREFUGFdNyLENACAIRcHHjFTKlpSsxAgU38RYeOUZsHgkYYC+2NbdqioiAknbZkaZibvfONE4HF29g5ebAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-hover-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAZCAYAAAAWuY87AAAAAXNSR0IArs4c6QAAAMxJREFUKFN10K1qhQAYh/HnXImiYDMaLAYxaPEjbgy2O9mqzSSYPDA3BcVksxqsRkHESxkI8+WEU3+8Dy/8b8AL8LttG5qmcQM+gHLfd1RVPeEduB/HgaIoJ5zJuq4YhiGwLAumaQpM04Rt2wLjOOK6rsAwDPi+L9D3PWEYCnRdRxRFAm3bPodP4KtpGpIkOZPnEMfx48X/lyt5gKqqCIJAkrIs8TxPoCgKHMcRyPMcy7IEsiy7BnoDvtM0vSZ8BX7qur5WP2GeZ3Rd5w+uCV7EJtS2qAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-hover-center: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAK5JREFUSEvt1DsKhTAQBVDHCMEYsv8uK8gGBDvrtLYBJUUKqwii6Lw1THGtXuq5HOZD6H1fJqIG+eh5Hjxy3zceua6L27ZFTquh8zzxyHEc3HUdtpN937nveyxSSuFhGLBIzpmttVhk2zY2xmCRdV2/QeCLTynhF/9HJOdIn4xrWRb8txJjxCPzPONPeBxHfCchBDzivWfnnOQixbU0TRNrrcVBSYBqrayUkmTEtT8zlFyirjB1nQAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-hover-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAZCAYAAAAWuY87AAAAAXNSR0IArs4c6QAAAN9JREFUKFOt0D+uRlAQBfCxJOuwAXmd1gI0CsHXiuI2Onok4vqXS++KhsYKrEAlUcwXd4qX17/2l3PmJKMhIm7bBrquAyL+aM/zoBACDMN4wdLu+8aqqsA0TYLrurAsS7AsiyrneWJd12DbNsFxHNg0DTiOQ7DvuwLXdQnWdcWu68DzPIJlWbBtWwiC4BfeShiGBFJKlfgDb+Lz+VBinmeV+FeYpgn7vocoimiFc65WkiQhyPNcQZZlBGmaKiiKgoAxhsMwwIsKfN9XR6WU9NM4jtU/xnEkEEIg5xwYYwq+Dy4CsuIYAB0AAAAASUVORK5CYII=);
--bitmap-wasabi-button-hover-bottom-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADBJREFUGFdjZGBgiGRgYFh25MgRBkNDQwZGmMDx48cZVFRUEAIMUMD4//9/kBY4AADKcwv4UhodeAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-hover-bottom: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAECAYAAAB7oZQmAAAAAXNSR0IArs4c6QAAAC5JREFUKFO1zTERADAMA7F3ziTLH0K2LC4Ki4B0d5kZmrS7sd08EJDqACjJaycf12oMMRBtAH0AAAAASUVORK5CYII=);
--bitmap-wasabi-button-hover-bottom-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADdJREFUGFc1yLENACEMxdDPmLBT+igbZJUUWckIuHNj6Y2qIjNlZgLW6G4iQu7+QBL6ugDMH843tvYcXTJxSOUAAAAASUVORK5CYII=);
--bitmap-wasabi-button-pressed-top-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAClJREFUGFdjZGBgiGRAAoxQgWUwMbiAtbU1AzMzMwNcwM7OjsHd3Z0BAIHtBaQVz8CyAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-pressed-top: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAECAYAAAB7oZQmAAAAAXNSR0IArs4c6QAAAC1JREFUKFO1z0ERAEAMwsD0iyh8nM/qo6eCGNjJAI9yA1zZYGz3kd09SbWZJHxWtAlJomz0sAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-pressed-top-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADZJREFUGFdNyEENAEEIBMHGGD52lGIAOzggs8nmHlfPCuDwsU0A/oUiM727dDe2FVXlmUHSiwvArBxdq7+9tAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-pressed-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAZCAYAAAAWuY87AAAAAXNSR0IArs4c6QAAAJFJREFUKFOtzrEKgzAAhOHf13JQkAwBESOSoaHYOroY9AXaN2lB2ucsyVA4unb9uDuuAE7Au6oqpmmiAK7AI0GMMcMFeCbYti3Dt7Lvu8K6rgrLsijM86wQQlDw3iuM46gwDIOCcy7DDbinpz/Q970m/gFt2+qotVbBGKPQNE2GM3Ckp3VdZwjAK0FZlgpd1/EB+JlD+hxfRnUAAAAASUVORK5CYII=);
--bitmap-wasabi-button-pressed-center: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAAXNSR0IArs4c6QAAAP5JREFUSEu1lruqhTAQRSOW/j/WYmVpaWkniIUIIihiIT4TxUIUX3PJcDl/sAfSZrFmZ5IYVVWRaZoCUUQk9DL6vifLshAM8X2feN9XGPM8Q02e5xGGUorO84SY6FYxREpJx3FgIeM40rZtMAhn0nUdLcsCg+jwjbZtaZomLKRpGiiE21XXNQ3DADHRmzKkLEu8SVEUJKWEmOg54eDzPCelFBaSZRmt6wqDcCZpmuKHMUkS2vcdaxLHMfTu4nZFUUTXdWFNwjCEQvgIB0FA931DTH4T7/s+aRqq+NHyPI/0ZCJK78vBO44DM/ldK7ZtYzT+W8PBu64L+61ogLb5A1LKF+DsoBz6AAAAAElFTkSuQmCC);
--bitmap-wasabi-button-pressed-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAZCAYAAAAWuY87AAAAAXNSR0IArs4c6QAAAPZJREFUKFOt0KuKhHAUBvDjE/kGzgsYTQuGBWEQ0WKaYrIJg0mTJpPJ5g0Mkyw2LzNewxj1Gc4y/wO7L7D1x/d9cA7XdR0exwGXywUQ8Yvb9x3P8wSe5z/wzW3bhu/3GwRBIHg+nzjPM4iiSJXPxjAMIEkSQdM02Pc9yLJMUNc1tm0LiqIQPB4PVrlerwRVVeHr9QJVVQnKssRxHEHTNIKiKHCapj/I85wldF2nRJZlLPELaZoyMAyDEv8BSZLgsixgmiaNxnHM4Ha7EURRhOu6gmVZBGEYMrBtm8DzPNy2DRzHoZ+6rsvO932fIAgC7LoO7vc7gx8gwgKy/NjfHAAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-pressed-bottom-left: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAC9JREFUGFdjZGBgiGRgYFhmZ2fHEBsby8AIE7C2tmYIDQ1FCDBAAeP///9BWuAAAF0yCdmSvfTgAAAAAElFTkSuQmCC);
--bitmap-wasabi-button-pressed-bottom: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAECAYAAAB7oZQmAAAAAXNSR0IArs4c6QAAADNJREFUKFO1zTERADAMw0CHQFZjK8os4adey8ECoK/dpbuVClDNDLZTxv+WJKLCQ4CTRi6qaw3tfPg8uwAAAABJRU5ErkJggg==);
--bitmap-wasabi-button-pressed-bottom-right: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAADdJREFUGFc1yLENACEMxdDPmLBGigyXMl2KrGQE3Lmx9EZE0N1ydwFrZCZVJTN7IAl9XQDmD+cbuIIcXR1tpikAAAAASUVORK5CYII=);
}
</style>
<link rel="stylesheet" href="button.css" />
<style id="bitmap-css"></style>
<style id="truetypefont-css"></style>
</head>
<body>
<div style="width: 200px; image-rendering: pixelated;">
<button
id="switch"
class="webamp--img wasabi"
data-obj-name="WasabiButton"
style="
left: 0px;
top: calc(100% + -22px);
width: 100%;
height: 21px;
pointer-events: auto;
"
>
<span></span>
<span>Switch</span>
<span></span>
</button>
<hr/>
<p style="background-image: var(--bitmap-wasabi-button); height: 300px;">
</p>
</div>
</body>
</html>

View file

@ -0,0 +1,16 @@
button.wasabi {
background-image: none;
border: 4px solid transparent;
border-image-source: var(--bitmap-studio-button);
/* border-image-slice: 4 4 4 5 fill; */
border-image-slice: 4 fill;
vertical-align:middle;
}
/* button.wasabi:hover {
border-image-source: var(--bitmap-wasabi-button-hover);
} */
button.wasabi:active {
border-image-source: var(--bitmap-studio-button-pressed);
}

View file

@ -0,0 +1,96 @@
.list {
color: var(--color-studio-list-text, var(--color-wasabi-list-text));
background-color: var(--color-wasabi-list-background, transparent);
background-image: var(--bitmap-studio-list-background, none);
}
.list > * {
user-select: none;
}
.list .selected {
background-color: var(
--color-studio-list-item-selected,
var(--color-wasabi-list-text-selected-background)
);
color: var(
--color-studio-list-item-selected-fg,
var(--color-wasabi-list-text-selected)
);
}
/* == COLORTHEMELIST == */
colorthemeslist {
border: 1px solid black;
border: none;
}
colorthemeslist > select {
border: none;
}
/* == PLAYLIST == */
/* .pl.list {
pointer-events: all;
} */
.pl > .content-list {
margin-right: 15px;
max-height: 100%;
overflow: auto;
}
.pl > .content-list::-webkit-scrollbar {
display: none;
}
.pl::before,
.pl::after {
content: "";
position: absolute;
top: 0;
width: 15px;
height: 100%;
right: 0;
box-sizing: border-box;
background: var(--color-wasabi-window-background, transparent);
z-index: 0;
pointer-events: none;
}
.pl::after {
width: 8px;
right: 2px;
border-left: 1px solid
var(--color-wasabi-border-sunken, rgba(192, 192, 192, 0.8));
border-right: 1px solid
var(--color-wasabi-border-sunken, rgba(192, 192, 192, 0.8));
background: var(--color-wasabi-scrollbar-background-inverted, black);
}
.pl > slider {
z-index: 1;
}
.pl > slider::before /* button.wasabi */ {
box-sizing: border-box;
background-image: none;
border: 4px solid transparent;
border-image-source: var(--bitmap-studio-button);
/* border-image-slice: 4 4 4 5 fill; */
border-image-slice: 4 fill;
vertical-align: middle;
}
.pl > slider:active:before /* button.wasabi:active */ {
border-image-source: var(--bitmap-studio-button-pressed);
}
.pl > slider::after {
content: "";
position: absolute;
/* TODO: do centering it by calc the real grip's bitmap height/width. */
left: calc(var(--thumb-left) + 1px);
top: calc(var(--thumb-top) + 5px);
width: 6px;
height: 8px;
background-image: var(--bitmap-wasabi-scrollbar-vertical-grip);
}
.pl .current {
color: var(
--color-pledit-text-current,
var(--color-wasabi-list-text-current)
);
}

View file

@ -0,0 +1,76 @@
/* Let's get this party started */
/*? VERTICAL */
::-webkit-scrollbar {
width: var(--dim-vscrollbar-width);
background-image: var(--bitmap-wasabi-scrollbar-vertical-background);
}
/* Track */
/* ::-webkit-scrollbar-track {
background-image: var(--bitmap-wasabi-scrollbar-vertical-background);
} */
::-webkit-scrollbar-button {
background-image: var(--bitmap-wasabi-scrollbar-vertical-left);
}
::-webkit-scrollbar-button:vertical {
height: var(--dim-vscrollbar-btn-height);
}
::-webkit-scrollbar-button:vertical:increment {
background-image: var(--bitmap-wasabi-scrollbar-vertical-right);
}
/* Handle */
::-webkit-scrollbar-thumb {
background-image: var(
--bitmap-wasabi-scrollbar-vertical-button,
var(--bitmap-studio-scrollbar-vertical-button)
);
/*background-repeat: repeat;*/
}
::-webkit-scrollbar-thumb {
max-height: var(
--dim-vscrollbar-thumb-height,
var(--dim-vscrollbar-thumb-height2)
);
min-height: var(
--dim-vscrollbar-thumb-height,
var(--dim-vscrollbar-thumb-height2)
);
background-repeat: no-repeat;
}
/* ::-webkit-scrollbar-thumb::after {
content: 'HALO';
position: absolute;
display: block;
inset: 0;
background: rgba(255, 230, 0, 1);
z-index: 100;
} */
/* ::-webkit-scrollbar-thumb:window-inactive {
background: rgba(255,0,0,0.4);
} */
/*? HORIZONTAL */
::-webkit-scrollbar:horizontal {
height: var(--dim-hscrollbar-height);
background-image: var(--bitmap-wasabi-scrollbar-horizontal-background);
}
/* Track */
/* ::-webkit-scrollbar-track:horizontal {
} */
::-webkit-scrollbar-button:horizontal {
background-image: var(--bitmap-wasabi-scrollbar-horizontal-left);
width: var(--dim-hscrollbar-btn-width);
}
::-webkit-scrollbar-button:horizontal:increment {
background-image: var(--bitmap-wasabi-scrollbar-horizontal-right);
}
/* Handle */
::-webkit-scrollbar-thumb:horizontal {
background-image: var(--bitmap-studio-scrollbar-horizontal-button);
}
::-webkit-scrollbar-corner {
background: transparent;
}
/* ---------- EOF SCROLLBAR ------------ */

View file

@ -10,17 +10,29 @@
body {
margin: 0;
background-color: rgb(58, 110, 165);
font-family: Arial, Helvetica, sans-serif;
}
* {
box-sizing: border-box;
}
textarea,
group,
text,
input,
select {
font-size: 10.5px;
user-select: none;
}
textarea:focus,
input:focus,
select:focus {
outline: none;
}
#ui-root
select {
background-color: transparent;
color:var(--color-studio-list-text, var(--color-wasabi-list-text));
}
select option {
padding-left: 5px;
width: 300%;
@ -89,7 +101,7 @@
slider > div {
display: none;
}
slider::after {
slider::before {
content: "";
position: absolute;
left: var(--thumb-left);
@ -98,13 +110,13 @@
height: var(--thumb-height);
background-image: var(--thumb-background-image);
}
slider:hover:after {
slider:hover:before {
background-image: var(
--thumb-hover-background-image,
var(--thumb-background-image)
);
}
slider:active:after {
slider:active:before {
background-image: var(
--thumb-down-background-image,
var(--thumb-background-image)
@ -121,6 +133,7 @@
font-style: normal;
}
text wrap {
display: block;
background-image: inherit;
background-size: 0px;
position: relative;
@ -133,17 +146,18 @@
display: flex;
/* vertical align: */
align-items: center;
justify-content: center;
justify-content: var(--align, center);
}
text span {
user-select: none;
pointer-events: none;
display: inline-block;
/* display: inline-block; */
background-image: inherit;
/* vertical-align: bottom; */
color: transparent;
width: var(--charwidth);
height: var(--charheight);
margin-right: var(--hspacing, 0);
background-position-x: var(--x);
background-position-y: var(--y);
overflow: hidden;
@ -159,7 +173,7 @@
progressgrid,
group,
layer,
animatedlayer,
animatedlayer, vis,
button,
slider,
text,
@ -214,19 +228,15 @@
transition: opacity var(--fade-out-speed, 0.25);
}
.wasabi-button {
border: 3px solid transparent;
--border-image: var(--bitmap-studio-button);
border-image: var(--border-image) 3 fill/ 1 / 0px stretch;
}
.wasabi-button:active {
--border-image: var(--bitmap-studio-button-pressed);
}
.autowidthsource {
width: auto;
}
/* .pl {
background: white;
color: black;
} */
/* titleBar active state */
[inactivealpha="0"] {
opacity: 0;
@ -250,6 +260,9 @@
transition: width 0.1s, height 0.1s, left 0.1s, top 0.1s;
}
</style>
<link rel="stylesheet" href="css/list.css" />
<link rel="stylesheet" href="css/button.css" />
<link rel="stylesheet" href="css/scrollbar.css" />
<style id="bitmap-css"></style>
<style id="truetypefont-css"></style>
</head>

View file

@ -24,16 +24,34 @@ function setStatus(status: string) {
const DEFAULT_SKIN = "assets/WinampModern566.wal";
async function main() {
// Purposefully don't await, let this load in parallel.
initializeSkinListMenu();
setStatus("Downloading skin...");
const skinPath = getUrlQuery(window.location, "skin") || DEFAULT_SKIN;
const response = await fetch(skinPath);
const data = await response.blob();
await loadSkin(data);
setStatus("Downloading MP3...");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
UI_ROOT.playlist.enqueuefile("assets/Just_Plain_Ant_-_05_-_Stumble.mp3");
setStatus("");
}
async function loadSkin(skinData: Blob) {
// Purposefully don't await, let this load in parallel.
initializeSkinListMenu();
UI_ROOT.reset();
document.body.appendChild(UI_ROOT.getRootDiv());
@ -103,11 +121,18 @@ async function initializeSkinListMenu() {
downloadLink.style.position = "absolute";
downloadLink.style.bottom = "0";
downloadLink.style.left = "320px";
downloadLink.text = "Download"
downloadLink.text = "Download";
const current = getUrlQuery(window.location, "skin");
for (const skin of data.data.modern_skins.nodes) {
const internalSkins = [
{ filename: "default", download_url: "" },
{ filename: "MMD3", download_url: "assets/MMD3.wal" },
];
const skins = [...internalSkins, ...data.data.modern_skins.nodes];
for (const skin of skins) {
const option = document.createElement("option");
option.value = skin.download_url;
option.textContent = skin.filename;

View file

@ -313,21 +313,6 @@ class Interpreter {
}
const obj = this.stack.pop();
// It is temporary patch until we can bind a
// singleton object to maki world.
// It is because maki think each class name below as a const.
if (
!obj.value &&
klass.name &&
[
"winampconfig",
"winampconfiggroup",
"configclass",
"config",
].includes(klass.name.toLowerCase())
) {
obj.value = new klass();
}
assert(
(obj.type === "OBJECT" && typeof obj.value) === "object" &&
obj.value != null,

View file

@ -16,6 +16,7 @@ export type Variable =
| {
type: "OBJECT";
value: BaseObject;
guid?: string;
}
| {
type: "NULL";

View file

@ -7,10 +7,10 @@ export const AUDIO_STOPPED = "stopped";
export const AUDIO_PLAYING = "playing";
export class AudioPlayer {
_input: HTMLInputElement = document.createElement("input");
_audio: HTMLAudioElement = document.createElement("audio");
_context: AudioContext;
__preamp: GainNode;
_analyser: AnalyserNode;
_bands: GainNode[] = [];
_source: MediaElementAudioSourceNode;
_eqValues: { [kind: string]: number } = {};
@ -19,8 +19,10 @@ export class AudioPlayer {
_isStop: boolean = true; //becaue we can't audio.stop() currently
_trackInfo: {};
_albumArtUrl: string = null;
_timeRemaining: boolean = false; //temporary. to show minus
//events aka addEventListener()
_eventListener: Emitter = new Emitter();
_vuMeter: number = 0;
constructor() {
this._context = this._context = new (window.AudioContext ||
@ -48,15 +50,39 @@ export class AudioPlayer {
document.body.addEventListener("click", resume, false);
document.body.addEventListener("keydown", resume, false);
}
this._audio.src = "assets/Just_Plain_Ant_-_05_-_Stumble.mp3";
//"https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Auto-Pilot_-_03_-_Seventeen.mp3";
this._input.type = "file";
this._source = this._context.createMediaElementSource(this._audio);
this.__preamp = this._context.createGain();
const connectionNodes: AudioNode[] = [this._source, this.__preamp];
// Create the analyser node for the visualizer
this._analyser = this._context.createAnalyser();
this._analyser.fftSize = 2048;
this._analyser.fftSize = 32;
// don't smooth audio analysis
// this._analyser.smoothingTimeConstant = 0.0;
const connectionNodes: AudioNode[] = [
this._source,
this.__preamp,
this._analyser,
];
const analyserNode = this._analyser;
//TODO: generate vuMeter only once needed.
const pcmData = new Float32Array(analyserNode.fftSize);
const onFrame = () => {
analyserNode.getFloatTimeDomainData(pcmData);
let sumSquares = 0.0;
for (let i = 0; i < pcmData.length; i++) {
const amplitude = pcmData[i];
sumSquares += amplitude * amplitude;
}
this._vuMeter = Math.sqrt(sumSquares / pcmData.length);
window.requestAnimationFrame(onFrame);
};
window.requestAnimationFrame(onFrame);
BANDS.forEach((band, i) => {
const filter = this._context.createBiquadFilter();
@ -85,17 +111,6 @@ export class AudioPlayer {
current = next;
}
// document.body.appendChild(this._input);
// TODO: dispose
this._input.onchange = (e) => {
const file = this._input.files[0];
if (file == null) {
return;
}
this._audio.src = URL.createObjectURL(file);
this.play();
};
//temporary: in the end of playing mp3, lets stop.
//TODO: in future, when ended: play next mp3
this._audio.addEventListener("ended", () => this.stop());
@ -112,6 +127,10 @@ export class AudioPlayer {
this._eventListener.off(event, callback);
}
setAudioSource(url: string) {
this._audio.src = url;
}
// 0-1
getVolume(): number {
return this._audio.volume;
@ -139,14 +158,6 @@ export class AudioPlayer {
this.trigger("statchanged");
}
eject() {
this._input.click();
}
next() {}
previous() {}
// 0-1
setVolume(volume: number) {
this._audio.volume = volume;
@ -160,9 +171,15 @@ export class AudioPlayer {
this._audio.currentTime = this._audio.duration * percent;
}
toggleRemainingTime() {
this._timeRemaining = !this._timeRemaining;
}
// In seconds
getCurrentTime(): number {
return this._audio.currentTime;
// return this._audio.currentTime;
return this._timeRemaining
? this._audio.currentTime - this._audio.duration
: this._audio.currentTime;
}
getCurrentTimePercent(): number {
@ -238,7 +255,7 @@ export class AudioPlayer {
}
}
onEqChange(kind: string, cb: () => void): () => void {
onEqChange(kind: string, cb: () => void): Function {
switch (kind) {
case "preamp":
case "1":
@ -284,8 +301,6 @@ export class AudioPlayer {
return dispose;
}
// Current track length in seconds
getLength(): number {
return this._audio.duration;

View file

@ -68,6 +68,14 @@ export default class Bitmap {
return this._height;
}
getLeft() {
return this._x;
}
getTop() {
return this._y;
}
getCSSVar(): string {
return this._cssVar;
}

View file

@ -17,7 +17,7 @@ for (const [line, chars] of CHARS.split("\n").entries()) {
export default class BitmapFont extends Bitmap {
_charWidth: number;
_charHeight: number;
_horizontalSpacing: number;
_horizontalSpacing: number = 0;
_verticalSpacing: number;
_externalBitmap: boolean = false; //? true == _file = another.bitmap.id
_bitmap: Bitmap = null; // the real external bitmap
@ -47,6 +47,10 @@ export default class BitmapFont extends Bitmap {
return true;
}
getHorizontalSpacing(): number {
return this._horizontalSpacing;
}
_setAsBackground(div: HTMLElement, prefix: string) {
if (this._externalBitmap) {
if (!this._bitmap) {

View file

@ -111,6 +111,7 @@ export default class ColorThemesList extends GuiObj {
draw() {
super.draw();
this._div.classList.add('list');
this._div.setAttribute("data-obj-name", "ColorThemes:List");
this._renderGammaSets();
}

View file

@ -67,6 +67,7 @@ export default class GammaGroup {
canvas.width = safeWidth;
canvas.height = safeHeight;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.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);

View file

@ -36,6 +36,10 @@ export default class ImageManager {
this._bitmaps[id] = bitmap;
}
isFilePathAdded(filePath:string) {
return Object.keys(this._pathofBitmap).includes(filePath)
}
// Ensure we've loaded the image into our image loader.
async loadUniquePaths() {
for (const filePath of Object.keys(this._pathofBitmap)) {

View file

@ -10,20 +10,19 @@ export default class Vm {
// This could easily become performance sensitive. We could make this more
// performant by normalizing some of these things when scripts are added.
dispatch(object: BaseObject, event: string, args: Variable[] = []): number {
let ran = 0;
for (const [scriptId, script] of this._scripts.entries()) {
for (const script of this._scripts) {
for (const binding of script.bindings) {
if (
script.methods[binding.methodOffset].name === event &&
script.variables[binding.variableOffset].value === object
) {
const reversedArgs = [...args].reverse();
this.interpret(scriptId, binding.commandOffset, reversedArgs);
ran++;
this.interpret(script, binding.commandOffset, reversedArgs);
return 1
}
}
}
return ran
return 0
}
addScript(maki: ParsedMaki): number {
@ -32,8 +31,7 @@ export default class Vm {
return index;
}
interpret(scriptId: number, commandOffset: number, args: Variable[]) {
const script = this._scripts[scriptId];
interpret(script: ParsedMaki, commandOffset: number, args: Variable[]) {
interpret(commandOffset, script, args, classResolver);
}
}

View file

@ -68,6 +68,7 @@ export default class AnimatedLayer extends Layer {
this.gotoframe(frame);
UI_ROOT.vm.dispatch(this, "onplay");
if (frame === end) {
this.stop()
return;
}
this._animationInterval = setInterval(() => {
@ -76,6 +77,7 @@ export default class AnimatedLayer extends Layer {
if (frame === end) {
clearInterval(this._animationInterval);
this._animationInterval = null;
this.stop()
}
}, this._speed);
}

View file

@ -1,19 +1,16 @@
import UI_ROOT from "../../UIRoot";
/**
* This is the base class from which all other classes inherit.
*/
export default class BaseObject {
constructor() {
UI_ROOT.addObject(this);
}
static GUID = "516549714a510d87b5a6e391e7f33532";
/**
* Returns the class name for the object.
*
* @ret The class name.
*/
getClassName(): string {
throw new Error("Unimplemented");
getclassname(): string {
return this.constructor.name;
}
getId() {

View file

@ -108,25 +108,18 @@ export default class Button extends GuiObj {
}
setactivatednocallback(onoff: boolean){
//TODO:
if (onoff !== this._active) {
this._active = onoff;
if (this._active) {
this._div.classList.add("active");
} else {
this._div.classList.remove("active");
}
}
}
leftclick() {
this.onLeftClick();
if (this._action && this._actionTarget) {
const guiObj = this.findobject(this._actionTarget);
if (guiObj) {
guiObj.sendaction(
this._action,
this._param,
0,
0,
this._div.offsetLeft,
this._div.offsetTop,
this
);
}
}
}
onLeftClick() {
@ -136,12 +129,13 @@ export default class Button extends GuiObj {
handleAction(
action: string,
param: string | null = null,
actionTarget: string | null = null
actionTarget: string | null = null,
source: GuiObj = null
): boolean {
if (actionTarget) {
const guiObj = this.findobject(actionTarget);
if (guiObj) {
guiObj.handleAction(action, param);
guiObj.handleAction(action, param, null, this);
return true;
}
}

View file

@ -66,7 +66,8 @@ export default class ComponentBucket extends Group {
handleAction(
action: string,
param: string | null = null,
actionTarget: string | null = null
actionTarget: string | null = null,
source: GuiObj = null
) {
switch (action.toLowerCase()) {
case "cb_prev":

View file

@ -1,9 +1,10 @@
import XmlObj from "../XmlObj";
import BaseObject from "./BaseObject";
import ConfigItem from "./ConfigItem";
const _items : {[key:string]: ConfigItem} = {};
const _items: { [key: string]: ConfigItem } = {};
export default class ConfigClass {
export default class Config extends BaseObject {
static GUID = "593dba224976d07771f452b90b405536";
newitem(itemName: string, itemGuid: string): ConfigItem {
@ -15,10 +16,13 @@ export default class ConfigClass {
getitem(itemGuid: string): ConfigItem {
let cfg = _items[itemGuid];
if(!cfg){
if (!cfg) {
cfg = new ConfigItem();
_items[itemGuid] = cfg;
}
_items[itemGuid] = cfg;
}
return cfg;
}
}
// Global Singleton
export const CONFIG: Config = new Config();

View file

@ -69,13 +69,13 @@ 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",
"{0000000a-000c-0010-ff7b-01014263450c}": "vis", // visualization
"{45f3f7c1-a6f3-4ee6-a15e-125e92fc3f8d}": "pl", // playlist editor
"{6b0edf80-c9a5-11d3-9f26-00c04f39ffc6}": "ml", // media library
"{7383a6fb-1d01-413b-a99a-7e6f655f4591}": "con", // config?
"{7a8b2d76-9531-43b9-91a1-ac455a7c8242}": "lir", // lyric?
"{a3ef47bd-39eb-435a-9fb3-a5d87f6f17a5}": "dl", // download??
"{f0816d7b-fffc-4343-80f2-e8199aa15cc3}": "video",// independent video window
};
const guid = this._componentGuid;
this._componentAlias = knownContainerGuids[guid];

View file

@ -37,13 +37,21 @@ export default class GroupXFade extends Group {
handleAction(
action: string,
param: string | null = null,
actionTarget: string | null = null
) {
// if(action.toLowerCase().startsWith('switchto;')){
// UI_ROOT.vm.dispatch(this, 'onaction', [
// ])
// }
actionTarget: string | null = null,
source: GuiObj = null
): boolean {
if(action.toLowerCase().startsWith('switchto;')){
UI_ROOT.vm.dispatch(this, 'onaction', [
{ type: "STRING", value: action },
{ type: "STRING", value: param },
{ type: "INT", value: 0 },
{ type: "INT", value: 0 },
{ type: "INT", value: 0 },
{ type: "INT", value: 0 },
{ type: "OBJECT", value: source },
])
return true
}
switch (action.toLowerCase()) {
case "groupid":
// this._switchTo(action.toLowerCase());

View file

@ -446,6 +446,7 @@ export default class GuiObj extends XmlObj {
y >= this.gettop(),
"Expected click to be below the component's top"
);
this.getparentlayout().bringtofront()
UI_ROOT.vm.dispatch(this, "onleftbuttondown", [
{ type: "INT", value: x },
{ type: "INT", value: y },
@ -752,15 +753,10 @@ export default class GuiObj extends XmlObj {
handleAction(
action: string,
param: string | null = null,
actionTarget: string | null = null
actionTarget: string | null = null,
source: GuiObj = null
): boolean {
if (actionTarget) {
const guiObj = this.findobject(actionTarget);
if (guiObj) {
guiObj.handleAction(action, param);
return true;
}
}
// ancestor may override this function.
return false;
}
@ -783,7 +779,6 @@ export default class GuiObj extends XmlObj {
y: number,
p1: number,
p2: number,
source: GuiObj,
): number {
return UI_ROOT.vm.dispatch(this, "onaction", [
{ type: "STRING", value: action },
@ -792,7 +787,7 @@ export default class GuiObj extends XmlObj {
{ type: "INT", value: y },
{ type: "INT", value: p1 },
{ type: "INT", value: p2 },
{ type: "OBJECT", value: source },
{ type: "OBJECT", value: this },
]);
}

View file

@ -111,6 +111,10 @@ export default class Layout extends Group {
return true;
}
getscale(): number {
return 1.0;
}
init() {
super.init();
this._invalidateSize();

View file

@ -0,0 +1,186 @@
import { Emitter } from "../../utils";
import AUDIO_PLAYER from "../AudioPlayer";
// import BaseObject from "./BaseObject";
export type Track = {
filename: string; // full url, or just File.name
file?: File; // Blob
metadata?: string; // http://forums.winamp.com/showthread.php?t=345521
title?: string;
rating?: number; // 0..5
};
/**
* Non GUI element.
* Hold tracs.
* It still exist (not interfered) when skin changed
*/
export class PlEdit {
static GUID = "345beebc49210229b66cbe90d9799aa4";
// taken from lib/pldir.mi
static guid = "{345BEEBC-0229-4921-90BE-6CB6A49A79D9}";
_tracks: Track[] = [];
_currentIndex: number = -1;
_selection: number[] = [];
_eventListener: Emitter = new Emitter();
// shortcut of this.Emitter
on(event: string, callback: Function): Function {
return this._eventListener.on(event, callback);
}
trigger(event: string, ...args: any[]) {
this._eventListener.trigger(event, ...args);
}
off(event: string, callback: Function) {
this._eventListener.off(event, callback);
}
//? ======= General PlEdit Information =======
getnumtracks(): number {
return this._tracks.length;
}
getcurrentindex(): number {
return this._currentIndex;
}
getnumselectedtracks(): number {
return this._selection.length;
}
getnextselectedtrack(i: number): number {
const current = this._selection.indexOf(i);
const next = this._selection[current + 1];
return next;
}
//? ======= Manipulate PlEdit View =======
// Scrolls the PL to the currently playling
// item (mostly used with onKeyDown: space)
showcurrentlyplayingtrack(): void {
// return unimplementedWarning("showcurrentlyplayingtrack");
}
showtrack(item: number): void {
// return unimplementedWarning("showtrack");
}
addTrack(track: Track) {
this._tracks.push(track);
// set audio source if it is the first
if (this._tracks.length == 1) {
this.playtrack(0);
}
this.trigger("trackchange");
}
enqueuefile(file: string): void {
const newTrack: Track = { filename: file };
this.addTrack(newTrack);
}
clear(): void {
this._selection = [];
this._tracks = [];
this._currentIndex = null;
}
removetrack(item: number): void {
// return unimplementedWarning("removetrack");
}
swaptracks(item1: number, item2: number): void {
// return unimplementedWarning("swaptracks");
}
moveup(item: number): void {
// return unimplementedWarning("moveup");
}
movedown(item: number): void {
// return unimplementedWarning("movedown");
}
moveto(item: number, pos: number): void {
// return unimplementedWarning("moveto");
}
playtrack(item: number): void {
this._currentIndex = item;
const track = this._tracks[item];
const url = track.file ? URL.createObjectURL(track.file) : track.filename;
AUDIO_PLAYER.setAudioSource(url);
this.trigger("trackchange");
}
getCurrentTrackTitle(): string {
if (this._currentIndex < 0) {
return "";
}
return this.gettitle(this._currentIndex);
}
getrating(item: number): number {
return this._tracks[item].rating ?? 0;
}
setrating(item: number, rating: number): void {
this._tracks[item].rating = rating;
}
gettitle(item: number): string {
return this._tracks[item].filename.split("/").pop();
}
// getlength(item: number): string {
// // return unimplementedWarning("getlength");
// }
// getmetadata(item: number, metadatastring: string): string {
// // return unimplementedWarning("getmetadata");
// }
// getfilename(item: number): string {
// // return unimplementedWarning("getfilename");
// }
onpleditmodified(): void {
// return unimplementedWarning("onpleditmodified");
}
}
export class PlDir {
static GUID = "61a7abad41f67d7980e1d0b1f4a40386";
// taken from lib/pldir.mi
static guid = "{61A7ABAD-7D79-41f6-B1D0-E1808603A4F4}";
showcurrentlyplayingentry(): void {
// return unimplementedWarning("showcurrentlyplayingentry");
}
// getnumitems(): number {
// // return unimplementedWarning("getnumitems");
// }
// getitemname(item: number): string {
// // return unimplementedWarning("getitemname");
// }
refresh(): void {
// return unimplementedWarning("refresh");
}
renameitem(item: number, name: string): void {
// return unimplementedWarning("renameitem");
}
enqueueitem(item: number): void {
// return unimplementedWarning("enqueueitem");
}
playitem(item: number): void {
// return unimplementedWarning("playitem");
}
}

View file

@ -0,0 +1,118 @@
import UI_ROOT from "../../UIRoot";
import { removeAllChildNodes } from "../../utils";
import Group from "./Group";
import Slider, { ActionHandler } from "./Slider";
export default class PlayListGui extends Group {
static GUID = "pl";
static guid = "{45F3F7C1-A6F3-4EE6-A15E-125E92FC3F8D}";
_selectedIndex: number = -1;
_contentPanel: HTMLDivElement = document.createElement("div");
_slider: Slider = new Slider();
_sliderHandler: ActionHandler;
getElTag(): string {
return "group";
}
init() {
super.init();
UI_ROOT.playlist.on("trackchange", this.refresh);
}
_prepareScrollbar() {
this._slider.setXmlAttributes({
orientation: "v",
x: "-10",
relatx: "1",
y: "0",
w: "8",
h: "0",
relath: "1",
});
this._slider.setThumbSize(8, 18);
this._sliderHandler = new PlaylistScrollActionHandler(this._slider, this);
this._slider.setActionHandler(this._sliderHandler);
this._slider.getDiv().classList.add("scrollbar");
this._slider.draw();
this.addChild(this._slider);
this._contentPanel.addEventListener("scroll", this._contentScrolled);
}
_contentScrolled = () => {
const list = this._contentPanel;
const newPercent = list.scrollTop / (list.scrollHeight - list.clientHeight);
this._slider.setposition((1 - newPercent) * 255);
};
_scrollTo(percent: number) {
const list = this._contentPanel;
const newScrollTop = percent * (list.scrollHeight - list.clientHeight);
list.scrollTop = newScrollTop;
}
// experimental, brutal, just to see reflection of PlayList changes
refresh = () => {
removeAllChildNodes(this._contentPanel);
const pl = UI_ROOT.playlist;
const currentTrack = pl.getcurrentindex();
for (let i = 0; i < pl.getnumtracks(); i++) {
const line = document.createElement("div");
if (i == currentTrack) {
line.classList.add("current");
}
if (i == this._selectedIndex) {
line.classList.add("selected");
}
line.addEventListener("click", (ev: MouseEvent) => {
this._selectedIndex = i;
this.refresh();
});
line.addEventListener("dblclick", (ev: MouseEvent) => {
UI_ROOT.playlist.playtrack(i);
UI_ROOT.audio.play();
this.refresh();
});
line.textContent = `${i+1}. ${pl.gettitle(i)}`;
this._contentPanel.appendChild(line);
}
};
itemClick = () => {};
draw() {
super.draw();
this._prepareScrollbar();
this._div.appendChild(this._contentPanel);
this._contentPanel.classList.add("content-list");
this._div.setAttribute("tabindex", "0");
this._div.classList.add("pl");
this._div.classList.add("list");
this._div.style.pointerEvents = "auto";
}
}
class PlaylistScrollActionHandler extends ActionHandler {
_pl: PlayListGui;
_scrolling: boolean = false;
constructor(slider: Slider, pl: PlayListGui) {
super(slider);
this._pl = pl;
}
onLeftMouseDown(x: number, y: number): void {
this._scrolling = true;
}
onLeftMouseUp(x: number, y: number): void {
this._scrolling = false;
}
onsetposition(position: number): void {
if (this._scrolling) {
this._pl._scrollTo(1 - position / 255);
}
}
}

View file

@ -2,12 +2,12 @@ import UI_ROOT from "../../UIRoot";
import { assume, clamp, num, px, throttle } from "../../utils";
import GuiObj from "./GuiObj";
class ActionHandler {
export class ActionHandler {
_slider: Slider;
constructor(slider: Slider) {
this._slider=slider;
this._slider = slider;
}
_subscription: () => void = () => {};
_subscription: Function = () => {};
// 0-255
onsetposition(position: number): void {}
onLeftMouseDown(x: number, y: number): void {}
@ -22,6 +22,9 @@ class ActionHandler {
}
const MAX = 255;
// Note: FreeMouseMove is about receiving mousemove without mousedown precedent.
// It is useful for equalizer sliders that may changed once a slider is moved
// http://wiki.winamp.com/wiki/XML_GUI_Objects#.3Cslider.2F.3E_.26_.3CWasabi:HSlider.2F.3E_.26_.3CWasabi:VSlider.2F.3E
export default class Slider extends GuiObj {
static GUID = "62b65e3f408d375e8176ea8d771bb94a";
@ -39,46 +42,69 @@ export default class Slider extends GuiObj {
_thumbHeight: number = 0;
_position: number = 0;
_param: string | null = null;
// _thumbDiv: HTMLDivElement = document.createElement("div");
_actionHandler: null | ActionHandler;
_onSetPositionEvenEaten: number;
_mouseX: number;
_mouseY: number;
_mouseDx: number = 0; // mouseDown inside thumb. 0..thumbHeight
_mouseDy: number = 0;
getRealWidth() {
return this._div.getBoundingClientRect().width;
_getActualSize() {
return this._div.getBoundingClientRect();
}
/**
* Central logic of setting new position using mouse
*
* set .position by X, Y
* where X,Y is mouseEvent.offsetX & Y
* where X,Y is mouse position inside _div
*/
_setPositionXY(x: number, y: number) {
//TODO: consider padding. where padding = thumbSize/2
const width = this.getRealWidth() - this._thumbWidth;
const height = this.getheight() - this._thumbHeight;
if (this._vertical) {
y = y - this._thumbHeight / 2 - this._mouseDy;
} else {
x = x - this._thumbWidth / 2 - this._mouseDx;
}
const actual = this._getActualSize();
const width = actual.width - this._thumbWidth;
const height = actual.height - this._thumbHeight;
const newPercent = this._vertical ? (height - y) / height : x / width;
this._position = clamp(newPercent, 0, 1);
this._renderThumbPosition();
this.doSetPosition(this.getposition());
}
/**
* Part of central logic that detect whether mouseDown is inside thumb
* @param x mouse position inside _div
* @param y mouse position inside _div
*/
_checkMouseDownInThumb(x: number, y: number) {
if (this._vertical) {
const thumbTop = parseInt(
this._div.style.getPropertyValue("--thumb-top")
);
const dy = y - thumbTop;
this._mouseDy =
dy >= 0 && dy <= this._thumbHeight ? dy - this._thumbHeight / 2 : 0;
} else {
//? horizontal
const thumbLeft = parseInt(
this._div.style.getPropertyValue("--thumb-left")
);
const dx = x - thumbLeft;
this._mouseDx =
dx >= 0 && dx <= this._thumbWidth ? dx - this._thumbWidth / 2 : 0;
}
}
_registerDragEvents() {
// this._thumbDiv.addEventListener("mousedown", (downEvent: MouseEvent) => {
this._div.addEventListener("mousedown", (downEvent: MouseEvent) => {
downEvent.stopPropagation();
if (downEvent.button != 0) return; // only care LeftButton
// const bitmap = UI_ROOT.getBitmap(this._thumb);
//TODO: change client/offset into pageX/Y
const startX = downEvent.clientX;
const startY = downEvent.clientY;
const innerX = downEvent.offsetX;
const innerY = downEvent.offsetY;
// const width = this.getRealWidth() - this._thumbWidth;
// const height = this.getheight() - this._thumbHeight;
// const initialPostition = this._position;
// const newPercent = this._vertical ? startY / height : startX / width;
console.log("mouseDown:", downEvent.offsetX, downEvent.offsetY);
this._checkMouseDownInThumb(downEvent.offsetX, downEvent.offsetY);
this.doLeftMouseDown(downEvent.offsetX, downEvent.offsetY);
const handleMove = (moveEvent: MouseEvent) => {
@ -88,19 +114,11 @@ export default class Slider extends GuiObj {
const deltaX = newMouseX - startX;
const deltaY = newMouseY - startY;
// const deltaPercent = this._vertical ? deltaY / height : deltaX / width;
// const newPercent = this._vertical
// ? initialPostition - deltaPercent
// : initialPostition + deltaPercent;
// this._position = clamp(newPercent, 0, 1);
// this._renderThumbPosition();
// this.doSetPosition(this.getposition());
//below is mousePosition conversion relative to inner _div
this.doMouseMove(innerX + deltaX, innerY + deltaY);
};
const throttleMouseMove = throttle(handleMove,50)
const throttleMouseMove = throttle(handleMove, 50);
const handleMouseUp = (upEvent: MouseEvent) => {
upEvent.stopPropagation();
@ -182,7 +200,6 @@ export default class Slider extends GuiObj {
}
init() {
// console.log("SLIDER-INITED!");
this._initializeActionHandler();
this._registerDragEvents();
}
@ -227,11 +244,36 @@ export default class Slider extends GuiObj {
}
}
// called by playlist.scrollbar
setActionHandler(actionHandler: ActionHandler) {
if (this._actionHandler != null) {
this._actionHandler.dispose();
this._actionHandler = null;
}
this._actionHandler = actionHandler;
}
// called by playlist.scrollbar
setThumbSize(width: number, height: number) {
this._thumbWidth = width;
this._thumbHeight = height;
}
// extern Int Slider.getPosition();
getposition(): number {
return this._position * MAX;
}
/**
*
* @param newpos 0..MAX
*/
setposition(newpos: number) {
this._position= newpos / MAX;
this._renderThumbPosition();
this.doSetPosition(this.getposition());
// console.log("Slider.setPosition:", newpos);
}
onsetposition(newPos: number) {
this._onSetPositionEvenEaten = UI_ROOT.vm.dispatch(this, "onsetposition", [
//needed by seekerGhost
@ -261,7 +303,6 @@ export default class Slider extends GuiObj {
}
}
doLeftMouseUp(x: number, y: number) {
// console.log("slider.doLeftMouseUp");
UI_ROOT.vm.dispatch(this, "onleftbuttonup", [
{ type: "INT", value: x },
{ type: "INT", value: y },
@ -273,7 +314,6 @@ export default class Slider extends GuiObj {
{ type: "INT", value: this.getposition() },
]);
if (this._actionHandler != null) {
// console.log("slider_ACTION.doLeftMouseUp");
this._actionHandler.onLeftMouseUp(x, y);
}
}
@ -288,65 +328,46 @@ export default class Slider extends GuiObj {
}
}
_renderThumb() {
// this._thumbDiv.style.position = "absolute";
// this._thumbDiv.setAttribute("data-obj-name", "Slider::Handle");
_prepareThumbBitmaps() {
// this._thumbDiv.classList.add("webamp--img");
if (this._thumb != null) {
const bitmap = UI_ROOT.getBitmap(this._thumb);
// this._thumbDiv.style.width = px(bitmap.getWidth());
// this._thumbDiv.style.height = px(bitmap.getHeight());
// bitmap.setAsBackground(this._thumbDiv);
bitmap._setAsBackground(this._div, "thumb-");
this._div.style.setProperty("--thumb-width", px(bitmap.getWidth()));
this._div.style.setProperty("--thumb-height", px(bitmap.getHeight()));
}
this._div.style.setProperty("--thumb-width", px(this._thumbWidth));
this._div.style.setProperty("--thumb-height", px(this._thumbHeight));
if (this._downThumb != null) {
const bitmap = UI_ROOT.getBitmap(this._downThumb);
// bitmap.setAsDownBackground(this._thumbDiv);
bitmap._setAsBackground(this._div, "thumb-down-");
}
if (this._hoverThumb != null) {
const bitmap = UI_ROOT.getBitmap(this._hoverThumb);
// bitmap.setAsHoverBackground(this._thumbDiv);
bitmap._setAsBackground(this._div, "thumb-hover-");
}
}
_renderThumbPosition() {
if (this._thumb != null) {
// const bitmap = UI_ROOT.getBitmap(this._thumb);
// TODO: What if the orientation has changed?
if (this._vertical) {
const top =
(1 - this._position) * (this.getheight() - this._thumbHeight);
// this._thumbDiv.style.top = px(top);
this._div.style.setProperty("--thumb-top", px(top));
} else {
// const left = (1 - this._position * (this.getwidth() - bitmap.getWidth());
const curwidth = this.getRealWidth();
const left = this._position * (curwidth - this._thumbWidth);
// console.log('thumb.left', this._position, left, 'w:',this.getwidth(),'bmp.w:', bitmap.getWidth())
// this._thumbDiv.style.left = px(left);
this._div.style.setProperty("--thumb-left", px(left));
}
const actual = this._getActualSize();
if (this._vertical) {
const top = (1 - this._position) * (actual.height - this._thumbHeight);
this._div.style.setProperty("--thumb-top", px(Math.max(0, top)));
} else {
const left = this._position * (actual.width - this._thumbWidth);
this._div.style.setProperty("--thumb-left", px(left));
}
}
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._div.style.setProperty("--thumb-left", px(0));
this._div.style.setProperty("--thumb-top", px(0));
this._prepareThumbBitmaps();
this._renderThumbPosition();
// this._div.appendChild(this._thumbDiv);
}
@ -397,8 +418,8 @@ class SeekActionHandler extends ActionHandler {
_onAudioProgres = () => {
if (!this._pendingChange) {
if (this._slider.getId() == "seekerghost")
console.log("thumb: not isPending()!");
// if (this._slider.getId() == "seekerghost")
// console.log("thumb: not isPending()!");
this._slider._position = UI_ROOT.audio.getCurrentTimePercent();
// TODO: We could throttle this, or only render if the change is "significant"?
this._slider._renderThumbPosition();

View file

@ -7,6 +7,8 @@ import Group from "./Group";
import PRIVATE_CONFIG from "../PrivateConfig";
import UI_ROOT from "../../UIRoot";
import GuiObj from "./GuiObj";
import Config, { CONFIG } from "./Config";
import WinampConfig, { WINAMP_CONFIG } from "./WinampConfig";
import { AUDIO_PAUSED, AUDIO_STOPPED, AUDIO_PLAYING } from "../AudioPlayer";
@ -72,6 +74,16 @@ export default class SystemObject extends BaseObject {
}
initialVariable.value = this;
for (const vari of this._parsedScript.variables) {
if (vari.type == "OBJECT") {
if (vari.guid == Config.GUID) {
vari.value = CONFIG;
} else if (vari.guid == WinampConfig.GUID) {
vari.value = WINAMP_CONFIG;
}
}
}
UI_ROOT.vm.addScript(this._parsedScript);
UI_ROOT.vm.dispatch(this, "onscriptloaded");
}
@ -371,7 +383,7 @@ export default class SystemObject extends BaseObject {
* @param str The string.
*/
strlen(str: string): number {
return str.length;
return str ? str.length : 0;
}
/**
@ -641,7 +653,7 @@ export default class SystemObject extends BaseObject {
* @ret The value of the left vu meter.
*/
getleftvumeter(): number {
return 0;
return UI_ROOT.audio._vuMeter;
}
/**
@ -651,7 +663,7 @@ export default class SystemObject extends BaseObject {
* @ret The value of the right vu meter.
*/
getrightvumeter(): number {
return 0;
return UI_ROOT.audio._vuMeter;
}
/**
@ -770,6 +782,14 @@ export default class SystemObject extends BaseObject {
return "Niente da Caprie";
}
getplaylistlength(): number {
return UI_ROOT.playlist.getnumtracks();
}
getplaylistindex(): number {
return UI_ROOT.playlist.getcurrentindex();
}
/**
* getPlayItemMetaDataString()
*
@ -1621,10 +1641,10 @@ export default class SystemObject extends BaseObject {
//TODO:
}
istransparencyavailable():boolean {
return true
istransparencyavailable(): boolean {
return true;
}
translate(str: string): string {
return str;
}
@ -1638,6 +1658,9 @@ export default class SystemObject extends BaseObject {
iskeydown(vk: number): number {
return 0;
}
isminimized(): number {
return 0;
}
}
function dumpScriptDebug(script: ParsedMaki) {

View file

@ -18,11 +18,12 @@ export default class Text extends GuiObj {
_display: string;
_displayValue: string = "";
_disposeDisplaySubscription: () => void | null = null;
_disposeTrackChangedSubscription: () => void | null = null;
_text: string;
_bold: boolean;
_forceuppercase: boolean;
_forcelowercase: boolean;
_align: string;
_align: string = "center";
_font_id: string;
_font_obj: TrueTypeFont | BitmapFont;
_fontSize: number;
@ -36,6 +37,9 @@ export default class Text extends GuiObj {
_scrollPaused: boolean = false;
_scrollLeft: number = 0; // logically, not visually
_textFullWidth: number; //calculated, not runtime by css
_shadowColor: string;
_shadowX: number = 0;
_shadowY: number = 0;
_drawn: boolean = false; // needed to check has parents
constructor() {
@ -82,6 +86,7 @@ export default class Text extends GuiObj {
case "align":
// (str) One of the following three possible strings: "left" "center" "right" -- Default is "left."
this._align = value;
this._prepareCss();
break;
case "fontsize":
// (int) The size to render the chosen font.
@ -105,14 +110,29 @@ export default class Text extends GuiObj {
this._timeColonWidth = num(value);
this._prepareCss();
this._renderText();
break;
case "shadowcolor":
// (int) The comma delimited RGB color for underrendered shadow text.
this._shadowColor = value;
this._prepareCss();
break;
case "shadowx":
// (int) The x offset of the shadowrender.
this._shadowX = num(value);
this._prepareCss();
break;
case "shadowy":
// (int) The x offset of the shadowrender.
this._shadowY = num(value);
this._prepareCss();
break;
/*
antialias - (bool) Setting this flag causes the text to be rendered antialiased if possible.
default - (str) A parameter alias for text.
align - (str) One of the following three possible strings: "left" "center" "right" -- Default is "left."
valign - (str) One of the following three possible strings: "top" "center" "bottom" -- Default is "top."
shadowcolor - (int) The comma delimited RGB color for underrendered shadow text.
shadowx - (int) The x offset of the shadowrender.
shadowy - (int) The y offset of the shadowrender.
timeroffstyle - (int) How to display an empty timer: "0" = " : ", "1" = "00:00", and "2"="" (if one is displaying time)
nograb - (bool) Setting this flag will cause the text object to ignore left button down messages. Default is off.
showlen - (bool) Setting this flag will cause the text display to be appended with the length in minutes and seconds of the current song. Default is off.
@ -142,6 +162,20 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
}
}
// only applicable for TrueType Font
_autoDetectColor() {
if (this._color) {
if (this._color.split(",").length == 3) {
this._div.style.color = `rgb(${this._color})`;
return;
}
const color = UI_ROOT.getColor(this._color);
if (color) {
this._div.style.color = `var(${color.getCSSVar()}, ${color.getRgb()})`;
}
}
}
ensureFontSize() {
if (this._font_obj instanceof TrueTypeFont && this._fontSize) {
const canvas = document.createElement("canvas");
@ -163,8 +197,16 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
if (this._ticker && this._ticker != "off") {
this._prepareScrolling();
}
this._div.addEventListener("click", this._onClick);
}
_onClick = () => {
if (this._display.toLowerCase() == "time") {
UI_ROOT.audio.toggleRemainingTime();
this.setDisplayValue(integerToTime(UI_ROOT.audio.getCurrentTime()));
}
};
_setDisplay(display: string) {
if (display.toLowerCase() === this._display?.toLowerCase()) {
return;
@ -172,6 +214,9 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
if (this._disposeDisplaySubscription != null) {
this._disposeDisplaySubscription();
}
if (this._disposeTrackChangedSubscription != null) {
this._disposeTrackChangedSubscription();
}
this._display = display;
switch (this._display.toLowerCase()) {
case "":
@ -198,8 +243,14 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
// this._displayValue = "Niente da Caprie (3";
// break;
case "songtitle":
this._displayValue = "Your Favorite MP3 Song Title, U R Reading";
// this._displayValue = "Short MP3 Title";
this._displayValue = UI_ROOT.playlist.getCurrentTrackTitle();
this._disposeTrackChangedSubscription = UI_ROOT.playlist.on(
"trackchange",
() => {
this._displayValue = UI_ROOT.playlist.getCurrentTrackTitle();
this._renderText();
}
);
break;
case "songbitrate":
case "songsamplerate":
@ -219,6 +270,9 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
if (newValue !== this._displayValue) {
this._displayValue = newValue;
this._renderText();
UI_ROOT.vm.dispatch(this, "ontextchanged", [
{ type: "STRING", value: this.gettext() },
]);
}
}
@ -247,7 +301,8 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
// TODO
}
//to speedup, we spit render. This is only rendering style
//to speedup animation like text-scrolling, we spit rendering processes.
//This function is only rendering static styles
_prepareCss() {
if (!this._font_obj && this._font_id) {
this._font_obj = UI_ROOT.getFont(this._font_id);
@ -255,21 +310,29 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
const font = this._font_obj;
if (font instanceof BitmapFont) {
this._textWrapper.setAttribute("font", "BitmapFont");
//? font-size
this._div.style.setProperty(
"--fontSize",
(this._fontSize || "~").toString()
);
//? text-align
if (this._align != "center") {
this._div.style.setProperty("--align", this._align);
} else {
this._div.style.removeProperty("--align");
}
//? margin
this._div.style.setProperty("--hspacing", px(font.getHorizontalSpacing()));
this.setBackgroundImage(font);
this._div.style.backgroundSize = "0"; //disable parent background, because only children will use it
this._div.style.lineHeight = px(this._div.getBoundingClientRect().height);
this._div.style.setProperty("--charwidth", px(font._charWidth));
this._div.style.setProperty("--charheight", px(font._charHeight));
} else {
if (this._color) {
const color = UI_ROOT.getColor(this._color);
if (color) {
this._div.style.color = `var(${color.getCSSVar()}, ${color.getRgb()})`;
}
this._autoDetectColor();
if (this._shadowColor) {
this._div.style.textShadow = `${this._shadowX}px ${this._shadowY}px rgb(${this._shadowColor})`;
}
if (font instanceof TrueTypeFont) {
this._textWrapper.setAttribute("font", "TrueType");
@ -412,7 +475,7 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
}
doScrollText() {
const curL = this._scrollLeft;
const curL = this._scrollLeft;
const step = 1; //pixel
const idle = 20; //when overflow
const container = this._div.getBoundingClientRect();

View file

@ -28,6 +28,10 @@ export default class ToggleButton extends Button {
UI_ROOT.vm.dispatch(this, "ontoggle", [V.newBool(onoff)]);
}
onactivate(activated: number){
UI_ROOT.vm.dispatch(this, "onactivate", [{type: "INT", value: activated}]);
}
draw() {
super.draw();
this._div.setAttribute("data-obj-name", "ToggleButton");

View file

@ -1,28 +1,16 @@
import UI_ROOT from "../../UIRoot";
import Button from "./Button";
export default class WasabiButton extends Button {
// static GUID = "unknown";
_l: HTMLSpanElement = document.createElement("span");
_r: HTMLSpanElement = document.createElement("span");
_m: HTMLSpanElement = document.createElement("span");
// static GUID = "unknown";
getElTag(): string {
return "button";
}
getElTag():string{
return 'button';
}
constructor(){
super()
this._div.appendChild(this._l);
this._div.appendChild(this._m);
this._div.appendChild(this._r);
// this._image = 'studio.button'
// this._downimage = 'studio.button.pressed'
}
init(){
super.init();
this.setXmlAttr('image','studio.button')
this.setXmlAttr('downimage', 'studio.button.pressed');
constructor() {
super();
this.registerDimensions();
}
setXmlAttr(key: string, value: string): boolean {
@ -31,7 +19,7 @@ export default class WasabiButton extends Button {
}
switch (key) {
case "text":
this._m.innerText = value;
this._div.innerText = value;
break;
default:
return false;
@ -39,15 +27,20 @@ export default class WasabiButton extends Button {
return true;
}
registerDimensions() {
UI_ROOT.addHeight("button-border-top", "wasabi.button.top");
UI_ROOT.addHeight("button-border-bottom", "wasabi.button.bottom");
UI_ROOT.addWidth("button-border-left", "wasabi.button.left");
UI_ROOT.addWidth("button-border-right", "wasabi.button.right");
}
draw() {
super.draw();
this._div.classList.add('wasabi-button')
this._div.classList.remove("webamp--img");
this._div.classList.add("wasabi");
this._div.setAttribute("data-obj-name", "WasabiButton");
}
// _renderBackground() {
// }
/*
extern ToggleButton.onToggle(Boolean onoff);
extern int TOggleButton.getCurCfgVal()

View file

@ -1,8 +1,9 @@
import BaseObject from "./BaseObject";
import ConfigItem from "./ConfigItem";
const _items: { [key: string]: ConfigItem } = {};
export default class WinampConfig {
export default class WinampConfig extends BaseObject {
static GUID = "b2ad3f2b4e3131ed95e96dbcbb55d51c";
// _items : {[key:string]: ConfigItem} = {};
@ -27,6 +28,5 @@ export class WinampConfigGroup {
}
}
// Global Singleton for now
// export const Config = new ConfigClass();
// export Config;
// Global Singleton
export const WINAMP_CONFIG: WinampConfig = new WinampConfig();

View file

@ -0,0 +1,53 @@
import Group from "./Group";
import UI_ROOT from "../../UIRoot";
import { num } from "../../utils";
export default class XuiElement extends Group {
__inited: boolean = false;
_unhandledXuiParams: { key: string; value: string }[] = []; //https://github.com/captbaritone/webamp/pull/1161#discussion_r830527754
// _content: string;
// _shade: string;
// _padtitleleft: string;
// _padtitleright: string;
getElTag(): string {
return "group";
}
setXmlAttr(_key: string, value: string): boolean {
const lowerkey = _key.toLowerCase();
// console.log('wasabi:frame.key=',lowerkey,':=', value)
if (super.setXmlAttr(lowerkey, value)) {
return true;
}
this._unhandledXuiParams.push({ key: lowerkey, value });
return true;
}
init() {
if (this.__inited) return;
this.__inited = true;
super.init();
for (const systemObject of this._systemObjects) {
this._unhandledXuiParams.forEach(({ key, value }) => {
UI_ROOT.vm.dispatch(systemObject, "onsetxuiparam", [
{ type: "STRING", value: key },
{ type: "STRING", value: value },
]);
});
// ["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 },
// ]);
// }
// });
}
this._unhandledXuiParams = [];
}
}

View file

@ -32,6 +32,9 @@ import WasabiTitle from "./makiClasses/WasabiTitle";
import ComponentBucket from "./makiClasses/ComponentBucket";
import GroupXFade from "./makiClasses/GroupXFade";
import { classResolver } from "./resolver";
import WasabiButton from "./makiClasses/WasabiButton";
import PlayListGui from "./makiClasses/PlayListGui";
import XuiElement from "./makiClasses/XuiElement";
function hack() {
// Without this Snowpack will try to treeshake out resolver causing a circular
@ -159,28 +162,29 @@ export default class SkinParser {
//? But in the same time we need to reduce code complexity
//? So, temporary we are trying to not do Promise.all
// if (this._phase == RESOURCE_PHASE) {
// return await Promise.all(
// node.children.map((child) => {
// if (child instanceof XmlElement) {
// // console.log('traverse->', parent.name, child.name)
// this._scanRes(child);
// return this.traverseChild(child, parent);
// }
// })
// );
// } else {
for (const child of node.children) {
if (child instanceof XmlElement) {
this._scanRes(child);
await this.traverseChild(child, parent);
if (this._phase == RESOURCE_PHASE) {
return await Promise.all(
node.children.map((child) => {
if (child instanceof XmlElement) {
// console.log('traverse->', parent.name, child.name)
this._scanRes(child);
return this.traverseChild(child, parent);
}
})
);
} else {
for (const child of node.children) {
if (child instanceof XmlElement) {
this._scanRes(child);
await this.traverseChild(child, parent);
}
}
}
// }
}
async traverseChild(node: XmlElement, parent: any) {
switch (node.name.toLowerCase()) {
const tag = node.name.toLowerCase();
switch (tag) {
case "albumart":
return this.albumart(node, parent);
case "wasabixml":
@ -263,9 +267,17 @@ export default class SkinParser {
case "wasabi:medialibraryframe:nostatus":
case "wasabi:playlistframe:nostatus":
case "wasabi:standardframe:nostatus":
case "wasabi:standardframe:nostatus:short":
case "wasabi:standardframe:status":
case "wasabi:standardframe:modal:short":
case "wasabi:visframe:nostatus":
return this.wasabiFrame(node, parent);
case "buttonled":
case "fadebutton":
case "fadetogglebutton":
case "configcheckbox":
//temporary, to localize error
return this.dynamicXuiElement(node, parent)
case "componentbucket":
return this.componentBucket(node, parent);
case "playlisteditor":
@ -286,6 +298,9 @@ export default class SkinParser {
case "wrapper":
return this.traverseChildren(node, parent);
default:
// if(this._uiRoot.getXuiElement(tag)) {
// return this.dynamicXuiElement(node, parent)
// }
console.warn(`Unhandled XML node type: ${node.name}`);
return;
}
@ -357,6 +372,16 @@ export default class SkinParser {
this._uiRoot.addComponentBucket(bucket.getWindowType(), bucket);
}
async dynamicXuiElement(node: XmlElement, parent: any) {
const xuitag: string = node.name; // eg. Wasabi:MainFrame:NoStatus
const xuiEl: XmlElement = this._uiRoot.getXuiElement(xuitag);
if (xuiEl) {
const xuiFrame = new XmlElement("dummy", { id: xuiEl.attributes.id });
const Element:XuiElement = await this.newGroup(XuiElement, xuiFrame, parent);
Element.setXmlAttributes(node.attributes);
// await this.maybeApplyGroupDef(frame, xuiFrame);
}
}
async wasabiFrame(node: XmlElement, parent: any) {
const frame = new WasabiFrame();
this.addToGroup(frame, parent);
@ -584,7 +609,91 @@ export default class SkinParser {
this._res.bitmaps["studio.button.pressed.bottom"] = false;
this._res.bitmaps["studio.button.pressed.lowerRight"] = false;
return this.newGui(Button, node, parent);
await this.buildWasabiButtonFace();
return this.newGui(WasabiButton, node, parent);
}
async buildWasabiButtonFace() {
const face = this._uiRoot.getBitmap("studio.button");
// if (face == null && upperLeft !== null) {
if (!face) {
let upperLeft = this._uiRoot.getBitmap("studio.button.upperLeft");
if (upperLeft) {
//? default
let bottomRight = this._uiRoot.getBitmap("studio.button.lowerRight");
let dict: {
[attrName: string]: string;
} = {
id: "studio.button",
file: upperLeft.getFile(),
x: String(upperLeft.getLeft()),
y: String(upperLeft.getTop()),
w: String(
bottomRight.getLeft() - upperLeft.getLeft() + bottomRight.getWidth()
),
h: String(
bottomRight.getTop() - upperLeft.getTop() + bottomRight.getHeight()
),
};
const btnFace = new XmlElement("bitmap", { ...dict });
await this.bitmap(btnFace);
//? pressed
upperLeft = this._uiRoot.getBitmap("studio.button.pressed.upperLeft");
bottomRight = this._uiRoot.getBitmap(
"studio.button.pressed.lowerRight"
);
dict = {
id: "studio.button.pressed",
file: upperLeft.getFile(),
x: String(upperLeft.getLeft()),
y: String(upperLeft.getTop()),
w: String(
bottomRight.getLeft() - upperLeft.getLeft() + bottomRight.getWidth()
),
h: String(
bottomRight.getTop() - upperLeft.getTop() + bottomRight.getHeight()
),
};
const btnPressedFace = new XmlElement("bitmap", { ...dict });
await this.bitmap(btnPressedFace);
} else {
// we can't find ingredient, lets search the raw material
if (!this._imageManager.isFilePathAdded("window/window-elements.png"))
return;
//? default
let dict: {
[attrName: string]: string;
} = {
id: "studio.button",
file: "window/window-elements.png",
x: "1",
y: "135",
w: "31",
h: "31",
};
const btnFace = new XmlElement("bitmap", { ...dict });
await this.bitmap(btnFace);
//? pressed
dict = {
id: "studio.button.pressed",
file: "window/window-elements.png",
x: "67",
y: "135",
w: "31",
h: "31",
};
const btnPressedFace = new XmlElement("bitmap", { ...dict });
await this.bitmap(btnPressedFace);
}
//TODO: why this new created bitmap doesn't loaded?
await this._imageManager.loadUniquePaths();
await this._imageManager.ensureBitmapsLoaded();
}
}
async toggleButton(node: XmlElement, parent: any) {
@ -679,6 +788,12 @@ export default class SkinParser {
const groupDef = this._uiRoot.getGroupDef(groupdef_id);
if (groupDef != null) {
group.setXmlAttributes(groupDef.attributes);
if (groupDef.attributes.inherit_group) {
await this.maybeApplyGroupDefId(
group,
groupDef.attributes.inherit_group
);
}
await this.traverseChildren(groupDef, group);
// TODO: Maybe traverse groupDef's children?
}
@ -705,6 +820,13 @@ export default class SkinParser {
}
async component(node: XmlElement, parent: any) {
//TODO: parse dynamic element by guid value
if (
node.attributes.param == "guid:{45F3F7C1-A6F3-4ee6-A15E-125E92FC3F8D}"
) {
await this.buildWasabiButtonFace();
return this.newGui(PlayListGui, node, parent);
}
await this.traverseChildren(node, parent);
}
@ -716,9 +838,23 @@ export default class SkinParser {
}
async colorThemesList(node: XmlElement, parent: any) {
this.buildWasabiScrollbarDimension()
return this.newGui(ColorThemesList, node, parent);
}
buildWasabiScrollbarDimension() {
this._uiRoot.addWidth("vscrollbar-width", "wasabi.scrollbar.vertical.left");
this._uiRoot.addHeight("vscrollbar-btn-height", "wasabi.scrollbar.vertical.left");
this._uiRoot.addHeight("vscrollbar-thumb-height", "wasabi.scrollbar.vertical.button");
this._uiRoot.addHeight("vscrollbar-thumb-height2", "studio.scrollbar.vertical.button");
this._uiRoot.addHeight("hscrollbar-height", "wasabi.scrollbar.horizontal.left");
this._uiRoot.addWidth("hscrollbar-btn-width", "wasabi.scrollbar.horizontal.left");
this._uiRoot.addWidth("hscrollbar-thumb-width", "wasabi.scrollbar.horizontal.button");
this._uiRoot.addWidth("hscrollbar-thumb-width2", "studio.scrollbar.horizontal.button");
}
async layoutStatus(node: XmlElement, parent: any) {
assume(
node.children.length === 0,
@ -812,7 +948,7 @@ export default class SkinParser {
}
//replace children
mother.children.splice(0, mother.children.length, ...nonGroupDefs);
} //eof function
}; //eof function
// Note: Included files don't have a single root node, so we add a synthetic one.
// A different XML parser library might make this unnessesary.

View file

@ -1,4 +1,5 @@
import { getClass } from "../maki/objects";
import BaseObject from "./makiClasses/BaseObject";
import Button from "./makiClasses/Button";
import SystemObject from "./makiClasses/SystemObject";
import Container from "./makiClasses/Container";
@ -23,11 +24,13 @@ import WinampConfig, { WinampConfigGroup } from "./makiClasses/WinampConfig";
import ComponentBucket from "./makiClasses/ComponentBucket";
import AlbumArt from "./makiClasses/AlbumArt";
import Region from "./makiClasses/Region";
import { PlEdit, PlDir } from "./makiClasses/PlayList";
const CLASSES = [
BaseObject,
Config,
Config, ConfigItem, ConfigAttribute,
ConfigItem, ConfigAttribute,
WinampConfig, WinampConfigGroup,
ComponentBucket,
Region,
@ -48,6 +51,7 @@ const CLASSES = [
Timer,
Slider,
Vis,
PlEdit, PlDir,
GuiObj,
];

View file

@ -72,7 +72,7 @@ export function removeAllChildNodes(parent: Element) {
export function integerToTime(seconds: number): string {
const mins = Math.floor(seconds / 60);
const secs = String(Math.round(seconds % 60)).padStart(2, "0");
const secs = String(Math.abs(Math.round(seconds % 60))).padStart(2, "0");
return `${mins}:${secs}`;
}
@ -122,14 +122,14 @@ export class Emitter {
_cbs: { [event: string]: Array<Function> } = {};
// call this to register a callback to a specific event
on(event: string, cb: Function) {
on(event: string, cb: Function): Function {
if (this._cbs[event] == null) {
this._cbs[event] = [];
}
this._cbs[event].push(cb);
// return a function for later unregistering
return () => {
return () => {
//TODO: consider using this.off(), or integrate both
this._cbs[event] = this._cbs[event].filter((c) => c !== cb);
};