mirror of
https://github.com/captbaritone/webamp.git
synced 2026-01-23 02:15:01 +00:00
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:
parent
73fd41f273
commit
7451c6fe64
43 changed files with 1295 additions and 283 deletions
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
3
packages/webamp-modern/.gitignore
vendored
3
packages/webamp-modern/.gitignore
vendored
|
|
@ -1 +1,2 @@
|
|||
build/
|
||||
build/
|
||||
temp/
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -22,5 +22,6 @@ module.exports = {
|
|||
},
|
||||
buildOptions: {
|
||||
/* ... */
|
||||
out: './build'
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
83
packages/webamp-modern/src/css/button.check.html
Normal file
83
packages/webamp-modern/src/css/button.check.html
Normal 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>
|
||||
16
packages/webamp-modern/src/css/button.css
Normal file
16
packages/webamp-modern/src/css/button.css
Normal 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);
|
||||
}
|
||||
96
packages/webamp-modern/src/css/list.css
Normal file
96
packages/webamp-modern/src/css/list.css
Normal 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)
|
||||
);
|
||||
}
|
||||
76
packages/webamp-modern/src/css/scrollbar.css
Normal file
76
packages/webamp-modern/src/css/scrollbar.css
Normal 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 ------------ */
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export type Variable =
|
|||
| {
|
||||
type: "OBJECT";
|
||||
value: BaseObject;
|
||||
guid?: string;
|
||||
}
|
||||
| {
|
||||
type: "NULL";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -68,6 +68,14 @@ export default class Bitmap {
|
|||
return this._height;
|
||||
}
|
||||
|
||||
getLeft() {
|
||||
return this._x;
|
||||
}
|
||||
|
||||
getTop() {
|
||||
return this._y;
|
||||
}
|
||||
|
||||
getCSSVar(): string {
|
||||
return this._cssVar;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ export default class Layout extends Group {
|
|||
return true;
|
||||
}
|
||||
|
||||
getscale(): number {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
this._invalidateSize();
|
||||
|
|
|
|||
186
packages/webamp-modern/src/skin/makiClasses/PlayList.ts
Normal file
186
packages/webamp-modern/src/skin/makiClasses/PlayList.ts
Normal 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");
|
||||
}
|
||||
}
|
||||
118
packages/webamp-modern/src/skin/makiClasses/PlayListGui.ts
Normal file
118
packages/webamp-modern/src/skin/makiClasses/PlayListGui.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
53
packages/webamp-modern/src/skin/makiClasses/XuiElement.ts
Normal file
53
packages/webamp-modern/src/skin/makiClasses/XuiElement.ts
Normal 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 = [];
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue