mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-28 12:36:35 +00:00
merge from latest: x2nie-dev series: 12. (#1255)
This commit is contained in:
parent
65041ab4d6
commit
bb9fb130ef
21 changed files with 1397 additions and 531 deletions
|
|
@ -28,10 +28,24 @@ import Config from "./skin/makiClasses/Config";
|
|||
import WinampConfig from "./skin/makiClasses/WinampConfig";
|
||||
import Avs from "./skin/makiClasses/Avs";
|
||||
|
||||
import { getWa5Popup } from "./skin/makiClasses/menuWa5";
|
||||
|
||||
import { SkinEngineClass } from "./skin/SkinEngine";
|
||||
import { FileExtractor } from "./skin/FileExtractor";
|
||||
import {
|
||||
FileExtractor,
|
||||
PathFileExtractor,
|
||||
ZipFileExtractor,
|
||||
} from "./skin/FileExtractor";
|
||||
import Application from "./skin/makiClasses/Application";
|
||||
|
||||
import {
|
||||
getSkinEngineClass,
|
||||
getSkinEngineClassByContent,
|
||||
SkinEngine,
|
||||
} from "./skin/SkinEngine";
|
||||
|
||||
export type Skin = string | { name: string; url: string };
|
||||
|
||||
export class UIRoot {
|
||||
_id: string;
|
||||
_application: Application;
|
||||
|
|
@ -40,6 +54,7 @@ export class UIRoot {
|
|||
_winampConfig: WinampConfig;
|
||||
|
||||
_div: HTMLDivElement = document.createElement("div");
|
||||
_mousePos: { x: number; y: number } = { x: 0, y: 0 };
|
||||
_imageManager: ImageManager;
|
||||
// Just a temporary place to stash things
|
||||
_bitmaps: { [id: string]: Bitmap } = {};
|
||||
|
|
@ -62,6 +77,8 @@ export class UIRoot {
|
|||
_xFades: GroupXFade[] = [];
|
||||
_input: HTMLInputElement = document.createElement("input");
|
||||
_skinInfo: { [key: string]: string } = {};
|
||||
_skin: Skin = { name: "", url: "" };
|
||||
_skins: Skin[] = [];
|
||||
_skinEngineClass: SkinEngineClass;
|
||||
_eventListener: Emitter = new Emitter();
|
||||
_additionalCss: string[] = [];
|
||||
|
|
@ -89,6 +106,7 @@ export class UIRoot {
|
|||
this._winampConfig = new WinampConfig(this);
|
||||
this.playlist = new PlEdit(this); // must be after _config.
|
||||
this.vm = new Vm(this);
|
||||
this.setlistenMouseMove(true);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
|
|
@ -516,7 +534,11 @@ export class UIRoot {
|
|||
cssEl.textContent = cssRules.join("\n");
|
||||
}
|
||||
|
||||
dispatch(action: string, param: string | null, actionTarget: string | null) {
|
||||
dispatch(
|
||||
action: string,
|
||||
param?: string | null,
|
||||
actionTarget?: string | null
|
||||
) {
|
||||
switch (action.toLowerCase()) {
|
||||
case "play":
|
||||
this.audio.play();
|
||||
|
|
@ -556,12 +578,42 @@ export class UIRoot {
|
|||
case "close":
|
||||
this.closeContainer();
|
||||
break;
|
||||
|
||||
case "menu":
|
||||
getWa5Popup(param, this).popatmouse();
|
||||
break;
|
||||
|
||||
case "controlmenu":
|
||||
getWa5Popup("ControlMenu", this).popatmouse();
|
||||
break;
|
||||
case "sysmenu":
|
||||
getWa5Popup("Main", this).popatmouse();
|
||||
break;
|
||||
case "pe_add":
|
||||
getWa5Popup("Add", this).popatmouse();
|
||||
break;
|
||||
case "pe_rem":
|
||||
getWa5Popup("Remove", this).popatmouse();
|
||||
break;
|
||||
case "pe_sel":
|
||||
getWa5Popup("Select", this).popatmouse();
|
||||
break;
|
||||
case "pe_misc":
|
||||
getWa5Popup("MiscOpt", this).popatmouse();
|
||||
break;
|
||||
case "pe_list":
|
||||
getWa5Popup("Playlist", this).popatmouse();
|
||||
break;
|
||||
default:
|
||||
assume(false, `Unknown global action: ${action}`);
|
||||
}
|
||||
}
|
||||
|
||||
getActionState(action: string, param: string, actionTarget: string): boolean {
|
||||
getActionState(
|
||||
action: string,
|
||||
param: string,
|
||||
actionTarget: string = ""
|
||||
): boolean {
|
||||
if (action != null) {
|
||||
switch (action.toLowerCase()) {
|
||||
case "eq_toggle":
|
||||
|
|
@ -653,6 +705,20 @@ export class UIRoot {
|
|||
}
|
||||
}
|
||||
|
||||
setlistenMouseMove(listen: boolean) {
|
||||
const update = (e: MouseEvent) => {
|
||||
this._mousePos = {
|
||||
// https://stackoverflow.com/questions/6073505/what-is-the-difference-between-screenx-y-clientx-y-and-pagex-y
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
};
|
||||
};
|
||||
window.document[`${listen ? "add" : "remove"}EventListener`](
|
||||
"mousemove",
|
||||
update
|
||||
);
|
||||
}
|
||||
|
||||
//? Zip things ========================
|
||||
/* because maki need to load a groupdef outside init() */
|
||||
_zip: JSZip;
|
||||
|
|
@ -700,69 +766,6 @@ export class UIRoot {
|
|||
return await this._fileExtractor.getFileAsBlob(filePath);
|
||||
}
|
||||
|
||||
// async getFileAsString(filePath: string): Promise<string> {
|
||||
// if (this._preferZip) {
|
||||
// return await this.getFileAsStringZip(filePath);
|
||||
// } else {
|
||||
// return await this.getFileAsStringPath(filePath);
|
||||
// }
|
||||
// }
|
||||
// async getFileAsBytes(filePath: string): Promise<ArrayBuffer> {
|
||||
// if (this._preferZip) {
|
||||
// return await this.getFileAsBytesZip(filePath);
|
||||
// } else {
|
||||
// return await this.getFileAsBytesPath(filePath);
|
||||
// }
|
||||
// }
|
||||
// async getFileAsBlob(filePath: string): Promise<Blob> {
|
||||
// if (this._preferZip) {
|
||||
// return await this.getFileAsBlobZip(filePath);
|
||||
// } else {
|
||||
// return await this.getFileAsBlobPath(filePath);
|
||||
// }
|
||||
// }
|
||||
|
||||
// async getFileAsStringZip(filePath: string): Promise<string> {
|
||||
// if (!filePath) return null;
|
||||
// const zipObj = getCaseInsensitiveFile(this._zip, filePath);
|
||||
// if (!zipObj) return null;
|
||||
// return await zipObj.async("text");
|
||||
// }
|
||||
|
||||
// async getFileAsBytesZip(filePath: string): Promise<ArrayBuffer> {
|
||||
// if (!filePath) return null;
|
||||
// const zipObj = getCaseInsensitiveFile(this._zip, filePath);
|
||||
// if (!zipObj) return null;
|
||||
// return await zipObj.async("arraybuffer");
|
||||
// }
|
||||
|
||||
// async getFileAsBlobZip(filePath: string): Promise<Blob> {
|
||||
// if (!filePath) return null;
|
||||
// const zipObj = getCaseInsensitiveFile(this._zip, filePath);
|
||||
// if (!zipObj) return null;
|
||||
// return await zipObj.async("blob");
|
||||
// }
|
||||
|
||||
// async getFileAsStringPath(filePath: string): Promise<string> {
|
||||
// const response = await fetch(this._skinPath + filePath);
|
||||
// return await response.text();
|
||||
// }
|
||||
|
||||
// async getFileAsBytesPath(filePath: string): Promise<ArrayBuffer> {
|
||||
// const response = await fetch(this._skinPath + filePath);
|
||||
// return await response.arrayBuffer();
|
||||
// }
|
||||
|
||||
// async getFileAsBlobPath(filePath: string): Promise<Blob> {
|
||||
// const response = await fetch(this._skinPath + filePath);
|
||||
// return await response.blob();
|
||||
// }
|
||||
|
||||
// getFileIsExist(filePath: string): boolean {
|
||||
// const zipObj = getCaseInsensitiveFile(this._zip, filePath);
|
||||
// return !!zipObj;
|
||||
// }
|
||||
|
||||
//? System things ========================
|
||||
/* because maki need to be run if not inside any Group @init() */
|
||||
addSystemObject(systemObj: SystemObject) {
|
||||
|
|
@ -782,7 +785,14 @@ export class UIRoot {
|
|||
}
|
||||
|
||||
setSkinInfo(skinInfo: { [key: string]: string }) {
|
||||
this._skinInfo = skinInfo;
|
||||
const url = this._skinInfo.url;
|
||||
this._skinInfo = { ...skinInfo, url };
|
||||
}
|
||||
setSkinUrl(url: string) {
|
||||
this._skinInfo.url = url;
|
||||
}
|
||||
getSkinUrl() {
|
||||
return this._skinInfo.url;
|
||||
}
|
||||
getSkinInfo(): { [key: string]: string } {
|
||||
return this._skinInfo;
|
||||
|
|
@ -791,6 +801,103 @@ export class UIRoot {
|
|||
return this.getSkinInfo()["name"];
|
||||
}
|
||||
|
||||
// THIS IS A BIG THING, MOVED HERE FROM AN AGNOSTIC SKIN ENGINES
|
||||
async switchSkin(skin: Skin) {
|
||||
//* getting skin engine is complicated:
|
||||
//* SkinEngine is not yet instanciated during looking for a skinEngine.
|
||||
//* If file extension is know then we loop for registered Engines
|
||||
//* But sometime (if its a `.zip` or a path `/`), we need to detect by
|
||||
//* if a file exist, with a name is expected by skinEngine
|
||||
|
||||
const parentDiv = this.getRootDiv().parentElement;
|
||||
this.reset();
|
||||
// this._parent.appendChild(this._uiRoot.getRootDiv());
|
||||
parentDiv.appendChild(this.getRootDiv());
|
||||
|
||||
const name = typeof skin === "string" ? skin : skin.name;
|
||||
const skinPath = typeof skin === "string" ? skin : skin.url;
|
||||
this.setSkinUrl(skinPath);
|
||||
|
||||
let skinFetched = false;
|
||||
let SkinEngineClass = null;
|
||||
|
||||
//? usually the file extension is explicitly for SkinEngine. eg: `.wal`
|
||||
let SkinEngineClasses = await getSkinEngineClass(skinPath);
|
||||
|
||||
//? when file extension is ambiguous eg. `.zip`, several
|
||||
//? skinEngines are supporting, but only one is actually working with.
|
||||
//? lets detect:
|
||||
if (SkinEngineClasses.length > 1) {
|
||||
await this._loadSkinPathToUiroot(skinPath, null);
|
||||
skinFetched = true;
|
||||
SkinEngineClass = await getSkinEngineClassByContent(
|
||||
SkinEngineClasses,
|
||||
skinPath,
|
||||
this
|
||||
);
|
||||
} else {
|
||||
SkinEngineClass = SkinEngineClasses[0];
|
||||
}
|
||||
if (SkinEngineClass == null) {
|
||||
throw new Error(`Skin not supported`);
|
||||
}
|
||||
|
||||
//? success found a skin-engine
|
||||
this.SkinEngineClass = SkinEngineClass;
|
||||
const parser: SkinEngine = new SkinEngineClass(this);
|
||||
if (!skinFetched) await this._loadSkinPathToUiroot(skinPath, parser);
|
||||
// await parser.parseSkin();
|
||||
await parser.buildUI();
|
||||
|
||||
// loadSkin(this._parent, skinPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time to load the skin file
|
||||
* @param skinPath url string
|
||||
* @param uiRoot
|
||||
* @param skinEngine An instance of SkinEngine
|
||||
*/
|
||||
private async _loadSkinPathToUiroot(
|
||||
skinPath: string,
|
||||
skinEngine: SkinEngine
|
||||
) {
|
||||
let response: Response;
|
||||
let fileExtractor: FileExtractor;
|
||||
//? pick one of correct fileExtractor
|
||||
|
||||
if (skinPath.endsWith("/")) {
|
||||
fileExtractor = new PathFileExtractor();
|
||||
} else {
|
||||
response = await fetch(skinPath);
|
||||
if (response.status == 404) {
|
||||
throw new Error(`Skin does not exist`);
|
||||
}
|
||||
if (skinEngine != null) {
|
||||
fileExtractor = skinEngine.getFileExtractor();
|
||||
}
|
||||
}
|
||||
if (fileExtractor == null) {
|
||||
if (response.headers.get("content-type").startsWith("application/")) {
|
||||
fileExtractor = new ZipFileExtractor();
|
||||
} else {
|
||||
fileExtractor = new PathFileExtractor();
|
||||
}
|
||||
}
|
||||
|
||||
await fileExtractor.prepare(skinPath, response);
|
||||
// const skinZipBlob = await response.blob();
|
||||
|
||||
// const zip = await JSZip.loadAsync(skinZipBlob);
|
||||
// uiRoot.setZip(zip);
|
||||
// } else {
|
||||
// uiRoot.setZip(null);
|
||||
// const slash = skinPath.endsWith("/") ? "" : "/";
|
||||
// uiRoot.setSkinDir(skinPath + slash);
|
||||
// }
|
||||
this.setFileExtractor(fileExtractor);
|
||||
}
|
||||
|
||||
set SkinEngineClass(Engine: SkinEngineClass) {
|
||||
this._skinEngineClass = Engine;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,4 +13,13 @@ button.wasabi {
|
|||
|
||||
button.wasabi:active {
|
||||
border-image-source: var(--bitmap-studio-button-pressed);
|
||||
}
|
||||
}
|
||||
|
||||
button.center_image::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: var(--background-image);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
body {
|
||||
margin: 0;
|
||||
background-color: rgb(58, 110, 165);
|
||||
background-image: url(img/wallpaper.png);
|
||||
/* background-image: url(img/wallpaper.png); */
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
#experimental {
|
||||
|
|
|
|||
|
|
@ -114,6 +114,16 @@ text wrap {
|
|||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
text wrap {
|
||||
margin-left: 2px;
|
||||
}
|
||||
text wrap[font="TrueType"] {
|
||||
/* needed for titlebar */
|
||||
font-size: 10.5px;
|
||||
line-height: 10px;
|
||||
vertical-align: var(--valign, center);
|
||||
text-align: var(--align, center);
|
||||
}
|
||||
text wrap[font="BitmapFont"] {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
|
|
@ -121,7 +131,6 @@ text wrap[font="BitmapFont"] {
|
|||
/* align-items: center; */
|
||||
align-items: var(--valign, center);
|
||||
justify-content: var(--align, center);
|
||||
margin-left: 2px;
|
||||
}
|
||||
text span {
|
||||
user-select: none;
|
||||
|
|
@ -148,6 +157,11 @@ menu {
|
|||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.popup hr {
|
||||
margin-block-start: 3px;
|
||||
margin-block-end: 3px;
|
||||
border-bottom: none;
|
||||
}
|
||||
/* frame2 {
|
||||
box-shadow: inset 0 0 5px red;
|
||||
} */
|
||||
|
|
@ -264,6 +278,7 @@ container:not(:active):not(container:focus-within) .webamp--img.inactivable {
|
|||
}
|
||||
|
||||
.resizing {
|
||||
position:fixed;
|
||||
border: 1px solid blue;
|
||||
background-color: rgba(74, 74, 251, 0.205);
|
||||
z-index: 1000;
|
||||
|
|
@ -311,6 +326,7 @@ menu > .popup{
|
|||
width: auto;
|
||||
display: inline-block;
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
font-size: 10.5px;
|
||||
}
|
||||
ul.popup-menu-container li {
|
||||
display: flex;
|
||||
|
|
@ -322,7 +338,7 @@ ul.popup-menu-container li > span {
|
|||
padding: 4px 0;
|
||||
}
|
||||
ul.popup-menu-container li:hover > span {
|
||||
background: blue;
|
||||
background: #316ac5;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
@ -341,6 +357,7 @@ ul.popup-menu-container li:hover > span {
|
|||
min-width: 15px;
|
||||
text-align: center;
|
||||
font-size: smaller;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* nested popup */
|
||||
|
|
@ -350,4 +367,18 @@ ul.popup-menu-container li:hover > span {
|
|||
}
|
||||
.popup-menu-container > li:hover > .popup-menu-container{
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
|
||||
/* COMPONENT BUCKET */
|
||||
componentbucket[id="component list"] wrapper button {
|
||||
width: 44px;
|
||||
height: 34px;
|
||||
/* border: none; */
|
||||
/* border: 2px solid fuchsia; */
|
||||
margin: 0 1px;
|
||||
position: unset;
|
||||
display: inline-block;
|
||||
/* left: unset; */
|
||||
/* top: unset; */
|
||||
/* background: red; */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,14 +109,22 @@ async function initializeSkinListMenu() {
|
|||
// MODERN
|
||||
{ filename: "[Winamp] default", download_url: "" },
|
||||
{ filename: "[Winamp] MMD3", download_url: "assets/skins/MMD3.wal" },
|
||||
{ filename: "[Winamp] MMD3+Thinger", download_url: "assets/skins/MMD3+Thinger/" },
|
||||
{
|
||||
filename: "[Winamp] MMD3+Thinger",
|
||||
download_url: "assets/skins/MMD3+Thinger/",
|
||||
},
|
||||
// { filename: "[Folder] MMD3", download_url: "assets/skins/extracted/MMD3/" },
|
||||
{ filename: "[Winamp] BigBento", download_url: "assets/skins/BigBento/" },
|
||||
{ filename: "CornerAmp_Redux", download_url: "assets/skins/CornerAmp_Redux.wal" },
|
||||
{
|
||||
filename: "CornerAmp_Redux",
|
||||
download_url: "assets/skins/CornerAmp_Redux.wal",
|
||||
},
|
||||
|
||||
|
||||
// CLASSIC
|
||||
{ filename: "[Winamp Classic]", download_url: "assets/skins/base-2.91.wsz" },
|
||||
{
|
||||
filename: "[Winamp Classic]",
|
||||
download_url: "assets/skins/base-2.91.wsz",
|
||||
},
|
||||
{
|
||||
filename: "[Winamp Classic] MacOSXAqua1-5",
|
||||
download_url: "assets/skins/MacOSXAqua1-5.698dd4ab.wsz",
|
||||
|
|
@ -131,7 +139,10 @@ async function initializeSkinListMenu() {
|
|||
filename: "[wmp] Quicksilver WindowsMediaPlayer!",
|
||||
download_url: "assets/skins/Quicksilver.wmz",
|
||||
},
|
||||
{ filename: "[wmp] Windows XP", download_url: "assets/skins/Windows-XP.wmz" },
|
||||
{
|
||||
filename: "[wmp] Windows XP",
|
||||
download_url: "assets/skins/Windows-XP.wmz",
|
||||
},
|
||||
{
|
||||
filename: "[wmp] Famous Headspace",
|
||||
download_url: "assets/skins/Headspace.wmz",
|
||||
|
|
@ -156,7 +167,10 @@ async function initializeSkinListMenu() {
|
|||
},
|
||||
|
||||
// K-JOFOL
|
||||
{ filename: "[K-Jofol] Default", download_url: "assets/skins/Default.kjofol" },
|
||||
{
|
||||
filename: "[K-Jofol] Default",
|
||||
download_url: "assets/skins/Default.kjofol",
|
||||
},
|
||||
{
|
||||
filename: "[K-Jofol] Illusion 1.0",
|
||||
download_url: "assets/skins/Illusion1-0.kjofol",
|
||||
|
|
@ -165,7 +179,10 @@ async function initializeSkinListMenu() {
|
|||
filename: "[K-Jofol] K-Nine 05r",
|
||||
download_url: "assets/skins/K-Nine05r.kjofol",
|
||||
},
|
||||
{ filename: "[K-Jofol] Limus 2.0", download_url: "assets/skins/Limus2-0.zip" },
|
||||
{
|
||||
filename: "[K-Jofol] Limus 2.0",
|
||||
download_url: "assets/skins/Limus2-0.zip",
|
||||
},
|
||||
|
||||
// SONIQUE
|
||||
{ filename: "[Sonique] Default", download_url: "assets/skins/sonique.sgf" },
|
||||
|
|
@ -177,17 +194,26 @@ async function initializeSkinListMenu() {
|
|||
filename: "[Sonique] Panthom (SkinBuilder)",
|
||||
download_url: "assets/skins/phantom.sgf",
|
||||
},
|
||||
{ filename: "[Sonique] ChainZ and", download_url: "assets/skins/ChainZ-and.sgf" },
|
||||
{
|
||||
filename: "[Sonique] ChainZ and",
|
||||
download_url: "assets/skins/ChainZ-and.sgf",
|
||||
},
|
||||
|
||||
// COWON JET-AUDIO
|
||||
{
|
||||
filename: "[JetAudio] Small Bar",
|
||||
download_url: "assets/skins/DefaultBar_s.jsk",
|
||||
},
|
||||
{ filename: "[Cowon JetAudio] Gold", download_url: "assets/skins/Gold.uib" },
|
||||
{
|
||||
filename: "[Cowon JetAudio] Gold",
|
||||
download_url: "assets/skins/Gold.uib",
|
||||
},
|
||||
|
||||
// AIMP
|
||||
{ filename: "[AIMP] Flo-4K", download_url: "assets/skins/AIMP-Flo-4K.acs5" },
|
||||
{
|
||||
filename: "[AIMP] Flo-4K",
|
||||
download_url: "assets/skins/AIMP-Flo-4K.acs5",
|
||||
},
|
||||
];
|
||||
|
||||
const skins = [...internalSkins, ...bankskin1];
|
||||
|
|
|
|||
|
|
@ -49,7 +49,13 @@ export async function interpret(
|
|||
uiRoot
|
||||
);
|
||||
interpreter.stack = stack;
|
||||
return await interpreter.interpret(start);
|
||||
try {
|
||||
return await interpreter.interpret(start);
|
||||
// return interpreter.interpret(start);
|
||||
} catch (error) {
|
||||
// console.warn('error while interpret', program.file, error)
|
||||
console.warn(`Stopped executing ${program.maki_id}.\n`, error);
|
||||
}
|
||||
}
|
||||
|
||||
function validateVariable(v: Variable) {
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export default class Bitmap {
|
|||
|
||||
const groupId = this.getGammaGroup();
|
||||
const gammaGroup = uiRoot._getGammaGroup(groupId);
|
||||
if (gammaGroup._value == "0,0,0") {
|
||||
if (gammaGroup._value == "0,0,0" && gammaGroup._gray == 0) {
|
||||
// triple zero meaning no gamma should be applied.
|
||||
// return bitmap.getCanvas().toDataURL();
|
||||
const url = await this.toDataURL(uiRoot);
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export default class SkinEngineWAL extends SkinEngine {
|
|||
async prepareArial() {
|
||||
const node: XmlElement = new XmlElement("truetypefont", {
|
||||
id: "Arial",
|
||||
family: "Arial",
|
||||
family: "Arial, 'Liberation Sans', 'DejaVu Sans'",
|
||||
});
|
||||
await this.trueTypeFont(node, null);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,11 @@ 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.
|
||||
async dispatch(object: BaseObject, event: string, args: Variable[] = []): number {
|
||||
async dispatch(
|
||||
object: BaseObject,
|
||||
event: string,
|
||||
args: Variable[] = []
|
||||
): number {
|
||||
const reversedArgs = [...args].reverse();
|
||||
let executed = 0;
|
||||
for (const script of this._scripts) {
|
||||
|
|
@ -83,7 +87,12 @@ export default class Vm {
|
|||
}
|
||||
|
||||
if (match) {
|
||||
await this.interpret(script, binding.commandOffset, event, reversedArgs);
|
||||
await this.interpret(
|
||||
script,
|
||||
binding.commandOffset,
|
||||
event,
|
||||
reversedArgs
|
||||
);
|
||||
// return 1;
|
||||
executed++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,30 @@ import { XmlElement } from "@rgrove/parse-xml";
|
|||
let BRING_LEAST: number = -1;
|
||||
let BRING_MOST_TOP: number = 1;
|
||||
|
||||
const globalMouseDown: Function[] = [];
|
||||
export const installGlobalMouseDown = (f: Function) => {
|
||||
if (!globalMouseDown.includes(f)) {
|
||||
globalMouseDown.push(f);
|
||||
}
|
||||
};
|
||||
export const uninstallGlobalMouseDown = (f: Function) => {
|
||||
const index = globalMouseDown.indexOf(f);
|
||||
if (index != -1) {
|
||||
// delete globalMouseDown[index]
|
||||
globalMouseDown.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
export const executeGlobalMouseDown = (e: MouseEvent) => {
|
||||
for (let i = 0; i < globalMouseDown.length; i++) {
|
||||
// console.log(typeof globalMouseDown[i], '>>', globalMouseDown[i]);
|
||||
globalMouseDown[i](e);
|
||||
}
|
||||
// for (let mousedown of globalMouseDown) {
|
||||
// mousedown(e)
|
||||
// }
|
||||
};
|
||||
|
||||
// http://wiki.winamp.com/wiki/XML_GUI_Objects#GuiObject_.28Global_params.29
|
||||
export default class GuiObj extends XmlObj {
|
||||
static GUID = "4ee3e1994becc636bc78cd97b028869c";
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import Container from "./Container";
|
|||
import { LEFT, RIGHT, TOP, BOTTOM, CURSOR, MOVE } from "../Cursor";
|
||||
import { px, unimplemented } from "../../utils";
|
||||
import { UIRoot } from "../../UIRoot";
|
||||
import PopupMenu from "./PopupMenu";
|
||||
import { forEachMenuItem, IMenuItem } from "./MenuItem";
|
||||
import { findAction } from "./menuWa5actions";
|
||||
|
||||
// > A layout is a special kind of group, which shown inside a container. Each
|
||||
// > layout represents an appearance for that window. Layouts give you the ability
|
||||
|
|
@ -17,6 +20,7 @@ export default class Layout extends Group {
|
|||
static GUID = "60906d4e482e537e94cc04b072568861";
|
||||
_resizingDiv: HTMLDivElement = null;
|
||||
_resizing: boolean = false;
|
||||
_resizing_start: DOMRect = null;
|
||||
_canResize: number = 0; // combination of 4 directions: N/E/W/S
|
||||
_scale: number = 1.0;
|
||||
_opacity: number = 1.0;
|
||||
|
|
@ -25,6 +29,7 @@ export default class Layout extends Group {
|
|||
_movingStartY: number;
|
||||
_moving: boolean = false;
|
||||
_snap = { left: 0, top: 0, right: 0, bottom: 0 };
|
||||
_shortcuts: { [key: string]: number } = {};
|
||||
|
||||
constructor(uiRoot: UIRoot) {
|
||||
super(uiRoot);
|
||||
|
|
@ -178,21 +183,33 @@ export default class Layout extends Group {
|
|||
h = this._minimumHeight ? Math.max(h, this._minimumHeight) : h;
|
||||
return h;
|
||||
};
|
||||
const container = this._parent;
|
||||
const r = this._div.getBoundingClientRect();
|
||||
// const container = this._parent;
|
||||
if (cmd == "constraint") {
|
||||
this._canResize = dx;
|
||||
} else if (cmd == "start") {
|
||||
this.bringtofront();
|
||||
const r = this._div.getBoundingClientRect();
|
||||
// r.x = container.getleft()
|
||||
// r.y = container.gettop()
|
||||
this._resizing_start = r;
|
||||
// this._resizing_o = r;
|
||||
this._resizing = true;
|
||||
this._resizingDiv = document.createElement("div");
|
||||
this._resizingDiv.className = "resizing";
|
||||
this._resizingDiv.style.cssText = "position:fixed;";
|
||||
this._resizingDiv.style.width = px(r.width);
|
||||
this._resizingDiv.style.height = px(r.height);
|
||||
this._resizingDiv.style.top = px(container.gettop());
|
||||
this._resizingDiv.style.left = px(container.getleft());
|
||||
this._div.appendChild(this._resizingDiv);
|
||||
// this._resizingDiv.style.cssText = "position:fixed;";
|
||||
// this._resizingDiv.style.width = px(r.width);
|
||||
// this._resizingDiv.style.height = px(r.height);
|
||||
// this._resizingDiv.style.top = px(container.gettop());
|
||||
// this._resizingDiv.style.left = px(container.getleft());
|
||||
const { left, top, width, height } = r;
|
||||
this._resizingDiv.style.cssText = `
|
||||
width: ${px(width)};
|
||||
height: ${px(height)};
|
||||
left: ${px(left)};
|
||||
top: ${px(top)};
|
||||
`;
|
||||
// this._div.appendChild(this._resizingDiv);
|
||||
document.body.appendChild(this._resizingDiv);
|
||||
} else if (dx == CURSOR && dy == CURSOR) {
|
||||
this._resizingDiv.style.cursor = cmd;
|
||||
} else if (cmd == "move") {
|
||||
|
|
@ -200,19 +217,34 @@ export default class Layout extends Group {
|
|||
return;
|
||||
}
|
||||
// console.log(`resizing dx:${dx} dy:${dy}`);
|
||||
if (this._canResize & RIGHT)
|
||||
this._resizingDiv.style.width = px(clampW(r.width + dx));
|
||||
if (this._canResize & BOTTOM)
|
||||
this._resizingDiv.style.height = px(clampH(r.height + dy));
|
||||
let { left, top, width, height, right, bottom } = this._resizing_start;
|
||||
if (this._canResize & RIGHT) width = clampW(width + dx);
|
||||
if (this._canResize & BOTTOM) height = clampH(height + dy);
|
||||
|
||||
if (this._canResize & LEFT) {
|
||||
this._resizingDiv.style.left = px(container.getleft() + dx);
|
||||
this._resizingDiv.style.width = px(clampW(r.width + -dx));
|
||||
width = clampW(width + -dx);
|
||||
let l = left + dx;
|
||||
if (l + width <= right) {
|
||||
left = l;
|
||||
} else {
|
||||
left = right - this._minimumWidth;
|
||||
}
|
||||
}
|
||||
if (this._canResize & TOP) {
|
||||
this._resizingDiv.style.top = px(container.gettop() + dy);
|
||||
this._resizingDiv.style.height = px(clampH(r.height + -dy));
|
||||
height = clampH(height + -dy);
|
||||
let t = top + dy;
|
||||
if (t + height <= bottom) {
|
||||
top = t;
|
||||
} else {
|
||||
top = bottom - this._minimumHeight;
|
||||
}
|
||||
}
|
||||
this._resizingDiv.style.cssText = `
|
||||
width: ${px(width)};
|
||||
height: ${px(height)};
|
||||
left: ${px(left)};
|
||||
top: ${px(top)};
|
||||
`;
|
||||
} else if (cmd == "final") {
|
||||
if (!this._resizing) {
|
||||
return;
|
||||
|
|
@ -259,4 +291,21 @@ export default class Layout extends Group {
|
|||
this._moving = false;
|
||||
}
|
||||
}
|
||||
|
||||
// MENU SHORTCUT HANDLER HERE ======================
|
||||
registerShortcuts(popup: PopupMenu) {
|
||||
forEachMenuItem(popup, (m: IMenuItem) => {
|
||||
if (m.shortcut) {
|
||||
this._shortcuts[m.shortcut] = m.id;
|
||||
}
|
||||
});
|
||||
// console.log('layout.shortcuts:', this._shortcuts)
|
||||
}
|
||||
|
||||
executeShorcut(shortcut: string) {
|
||||
const menuId = this._shortcuts[shortcut];
|
||||
const action = findAction(menuId);
|
||||
// console.log('Layout:', this._name, 'executing shortcut:',shortcut, '@action.id:', menuId, '=:', action )
|
||||
const invalidateRequired = action.onExecute(this._uiRoot);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,46 @@ import Group from "./Group";
|
|||
// import Button from "./Button";
|
||||
import Layer from "./Layer";
|
||||
import { getWa5Popup } from "./menuWa5";
|
||||
import { generatePopupDiv } from "./PopupMenu";
|
||||
import PopupMenu from "./PopupMenu";
|
||||
import {
|
||||
ICLoseablePopup,
|
||||
destroyActivePopup,
|
||||
generatePopupDiv,
|
||||
setActivePopup,
|
||||
} from "./MenuItem";
|
||||
import { findAction, updateActions } from "./menuWa5actions";
|
||||
|
||||
let ACTIVE_MENU_GROUP: string = "";
|
||||
// let ACTIVE_MENU: Menu = null;
|
||||
|
||||
/*function destroyActivePopup() {
|
||||
console.log('globalWindowClick')
|
||||
if (ACTIVE_MENU != null) {
|
||||
ACTIVE_MENU.doCloseMenu()
|
||||
}
|
||||
ACTIVE_MENU_GROUP = ''
|
||||
}
|
||||
|
||||
let globalClickInstalled = false;
|
||||
function installGlobalClickListener() {
|
||||
setTimeout(() => { // using promise to prevent immediately executing of globalWindowClick
|
||||
installGlobalMouseDown(destroyActivePopup); // call globalWindowClick on any GuiObj
|
||||
if (!globalClickInstalled) {
|
||||
document.addEventListener("mousedown", destroyActivePopup); // call globalWindowClick on document
|
||||
globalClickInstalled = true;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
function uninstallGlobalClickListener() {
|
||||
if (globalClickInstalled) {
|
||||
document.removeEventListener("mousedown", destroyActivePopup);
|
||||
globalClickInstalled = false;
|
||||
}
|
||||
|
||||
uninstallGlobalMouseDown(destroyActivePopup)
|
||||
}*/
|
||||
// http://wiki.winamp.com/wiki/XML_GUI_Objects#?
|
||||
export default class Menu extends Group {
|
||||
export default class Menu extends Group implements ICLoseablePopup {
|
||||
static GUID = "73c00594401b961f24671b9b6541ac27";
|
||||
//static GUID "73C00594-961F-401B-9B1B-672427AC4165";
|
||||
_normalId: string;
|
||||
|
|
@ -22,7 +58,8 @@ export default class Menu extends Group {
|
|||
_elHover: GuiObj;
|
||||
_elDown: GuiObj;
|
||||
_elImage: Layer;
|
||||
_popup: HTMLElement;
|
||||
_popup: PopupMenu;
|
||||
_popupDiv: HTMLElement;
|
||||
|
||||
setXmlAttr(_key: string, value: string): boolean {
|
||||
const key = _key.toLowerCase();
|
||||
|
|
@ -99,25 +136,67 @@ export default class Menu extends Group {
|
|||
}
|
||||
}
|
||||
}
|
||||
onLeftButtonDown(x: number, y: number) {
|
||||
super.onLeftButtonDown(x, y);
|
||||
this._showButton(this._elDown);
|
||||
}
|
||||
onEnterArea() {
|
||||
super.onEnterArea();
|
||||
this._showButton(this._elHover);
|
||||
this._div.classList.add("open");
|
||||
}
|
||||
onLeaveArea() {
|
||||
super.onLeaveArea();
|
||||
|
||||
doClosePopup() {
|
||||
this._showButton(this._elNormal);
|
||||
this._div.classList.remove("open");
|
||||
// ACTIVE_MENU = null;
|
||||
// document.removeEventListener("mousedown", globalWindowClick);
|
||||
// uninstallGlobalMouseDown(globalWindowClick)
|
||||
// uninstallGlobalClickListener()
|
||||
// ACTIVE_MENU_GROUP = ''
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init();
|
||||
onLeftButtonDown(x: number, y: number) {
|
||||
// super.onLeftButtonDown(x, y);
|
||||
// this._showButton(this._elDown);
|
||||
//? toggle dropdown visibility
|
||||
if (ACTIVE_MENU_GROUP != this._menuGroupId) {
|
||||
ACTIVE_MENU_GROUP = this._menuGroupId;
|
||||
destroyActivePopup();
|
||||
// setTimeout(() => {
|
||||
// installGlobalMouseDown(globalWindowClick);
|
||||
// }, 500);
|
||||
// installGlobalClickListener()
|
||||
setActivePopup(this);
|
||||
} else {
|
||||
ACTIVE_MENU_GROUP = null;
|
||||
// if (ACTIVE_MENU != null) {
|
||||
// ACTIVE_MENU.doCloseMenu()
|
||||
// }
|
||||
destroyActivePopup();
|
||||
}
|
||||
this.onEnterArea();
|
||||
}
|
||||
onEnterArea() {
|
||||
// super.onEnterArea();
|
||||
if (ACTIVE_MENU_GROUP == this._menuGroupId) {
|
||||
// if (ACTIVE_MENU != null) {
|
||||
// ACTIVE_MENU.doCloseMenu()
|
||||
// }
|
||||
destroyActivePopup();
|
||||
this._showButton(this._elDown);
|
||||
this._div.classList.add("open");
|
||||
|
||||
// ACTIVE_MENU = this;
|
||||
setActivePopup(this);
|
||||
} else {
|
||||
this._showButton(this._elHover);
|
||||
}
|
||||
}
|
||||
onLeaveArea() {
|
||||
// super.onLeaveArea();
|
||||
if (ACTIVE_MENU_GROUP != this._menuGroupId) {
|
||||
this._showButton(this._elNormal);
|
||||
// this._div.classList.remove("open");
|
||||
}
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup();
|
||||
// this.resolveButtonsAction();
|
||||
// this._uiRoot.vm.dispatch(this, "onstartup", []);
|
||||
this.getparentlayout().registerShortcuts(this._popup);
|
||||
}
|
||||
resolveButtonsAction() {
|
||||
//console.log('found img')
|
||||
|
|
@ -157,20 +236,45 @@ export default class Menu extends Group {
|
|||
// this._div.classList.remove("vertical");
|
||||
// }
|
||||
|
||||
|
||||
if(this._menuId.startsWith('WA5:')){
|
||||
const [,popupId] = this._menuId.split(':')
|
||||
const popupMenu = getWa5Popup(popupId)
|
||||
if (this._menuId.startsWith("WA5:")) {
|
||||
const [, popupId] = this._menuId.split(":");
|
||||
this._popup = getWa5Popup(popupId, this._uiRoot);
|
||||
|
||||
this.invalidatePopup();
|
||||
// function menuClick(id:number){
|
||||
// console.log('menu clicked:', id)
|
||||
// }
|
||||
this._popup = generatePopupDiv(popupMenu, (id:number) => console.log('menu clicked:', id))
|
||||
} else {
|
||||
this._popup = document.createElement("div");
|
||||
this._popup.classList.add("fake-popup");
|
||||
}
|
||||
this._popup.classList.add("popup");
|
||||
// this._appendChildrenToDiv(this._popup);
|
||||
this._div.appendChild(this._popup);
|
||||
}
|
||||
|
||||
/**
|
||||
* update the checkmark, enabled/disabled, etc
|
||||
*/
|
||||
invalidatePopup() {
|
||||
const self = this;
|
||||
if (this._popup) {
|
||||
// destroy old DOM
|
||||
if (this._popupDiv) {
|
||||
this._popupDiv.remove();
|
||||
}
|
||||
|
||||
updateActions(this._popup, this._uiRoot); // let winamp5 menus reflect the real config/condition
|
||||
|
||||
const menuItemClick = (id: number) => {
|
||||
console.log("menu clicked:", id);
|
||||
const action = findAction(id);
|
||||
const invalidateRequired = action.onExecute(self._uiRoot);
|
||||
if (invalidateRequired) self.invalidatePopup();
|
||||
};
|
||||
|
||||
this._popupDiv = generatePopupDiv(this._popup, menuItemClick);
|
||||
// } else {
|
||||
// this._popupDiv = document.createElement("div");
|
||||
// this._popupDiv.classList.add("fake-popup");
|
||||
// }
|
||||
this._popupDiv.classList.add("popup");
|
||||
// this._appendChildrenToDiv(this._popup);
|
||||
this._div.appendChild(this._popupDiv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
197
packages/webamp-modern/src/skin/makiClasses/MenuItem.ts
Normal file
197
packages/webamp-modern/src/skin/makiClasses/MenuItem.ts
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
import { installGlobalMouseDown, uninstallGlobalMouseDown } from "./GuiObj";
|
||||
|
||||
export interface IPopupMenu {
|
||||
children: MenuItem[];
|
||||
}
|
||||
|
||||
export type IMenuItem = {
|
||||
type: "menuitem";
|
||||
caption: string;
|
||||
id: number;
|
||||
checked: boolean;
|
||||
disabled?: boolean;
|
||||
shortcut?: string; // "Ctrl+Alt+Shift+A"
|
||||
keychar?: string; // 'p' of "&Play"
|
||||
invisible?: boolean; // special case to register shortcut only
|
||||
data?: { [key: string]: any }; // used by skin's popup item
|
||||
};
|
||||
|
||||
type IMenuSeparator = {
|
||||
type: "separator";
|
||||
};
|
||||
|
||||
type IMenuPopup = {
|
||||
type: "popup";
|
||||
caption: string;
|
||||
popup: IPopupMenu;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
children?: MenuItem[];
|
||||
};
|
||||
|
||||
export type MenuItem = IMenuItem | IMenuSeparator | IMenuPopup;
|
||||
|
||||
export interface ICLoseablePopup {
|
||||
doClosePopup: Function;
|
||||
}
|
||||
|
||||
// ################## Popup Utils ##########################33
|
||||
|
||||
let ACTIVE_POPUP: ICLoseablePopup = null;
|
||||
|
||||
export function destroyActivePopup() {
|
||||
console.log("globalWindowClick");
|
||||
if (ACTIVE_POPUP != null) {
|
||||
ACTIVE_POPUP.doClosePopup();
|
||||
}
|
||||
// ACTIVE_MENU_GROUP = ''
|
||||
uninstallGlobalClickListener();
|
||||
}
|
||||
|
||||
export function setActivePopup(popup: ICLoseablePopup) {
|
||||
ACTIVE_POPUP = popup;
|
||||
if (popup) {
|
||||
installGlobalClickListener();
|
||||
} else {
|
||||
uninstallGlobalClickListener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* if the active == popup => set null
|
||||
* @param popup
|
||||
*/
|
||||
export function deactivePopup(popup: ICLoseablePopup) {
|
||||
if (popup == ACTIVE_POPUP) {
|
||||
setActivePopup(null);
|
||||
}
|
||||
}
|
||||
|
||||
let globalClickInstalled = false;
|
||||
function installGlobalClickListener() {
|
||||
setTimeout(() => {
|
||||
// using promise to prevent immediately executing of globalWindowClick
|
||||
if (!globalClickInstalled) {
|
||||
installGlobalMouseDown(destroyActivePopup); // call globalWindowClick on any GuiObj
|
||||
document.addEventListener("mousedown", destroyActivePopup); // call globalWindowClick on document
|
||||
globalClickInstalled = true;
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function uninstallGlobalClickListener() {
|
||||
if (globalClickInstalled) {
|
||||
document.removeEventListener("mousedown", destroyActivePopup);
|
||||
globalClickInstalled = false;
|
||||
uninstallGlobalMouseDown(destroyActivePopup);
|
||||
}
|
||||
}
|
||||
|
||||
// ################## MenuItem Utils ##########################33
|
||||
|
||||
export function forEachMenuItem(popup: IPopupMenu, callback: Function) {
|
||||
for (const menu of popup.children) {
|
||||
if (menu.type == "menuitem") {
|
||||
callback(menu);
|
||||
} else if (menu.type == "popup") {
|
||||
forEachMenuItem(menu.popup, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ExtractedCaptions = {
|
||||
caption: string;
|
||||
shortcut?: string; // "Ctrl+Alt+Shift+A"
|
||||
keychar?: string; // 'p' of "&Play"
|
||||
};
|
||||
|
||||
export function extractCaption(text: string): ExtractedCaptions {
|
||||
// const result:ExtractedCaptions = {caption:text, shortcut:null, keychar:''}
|
||||
const [caption, shortcut] = text.split("\t");
|
||||
const keychar = caption.includes("&")
|
||||
? caption[caption.indexOf("&") + 1].toLowerCase()
|
||||
: "";
|
||||
return { caption, shortcut, keychar };
|
||||
}
|
||||
|
||||
export function generatePopupDiv(
|
||||
popup: IPopupMenu,
|
||||
callback: Function
|
||||
): HTMLElement {
|
||||
const root = document.createElement("ul");
|
||||
root.className = "popup-menu-container";
|
||||
// root.style.zIndex = "1000";
|
||||
// console.log('generating popup:', popup)
|
||||
for (const menu of popup.children) {
|
||||
// const menuitem = document.createElement("li");
|
||||
let item: HTMLElement;
|
||||
// root.appendChild(item);
|
||||
switch (menu.type) {
|
||||
case "menuitem":
|
||||
if (menu.invisible === true) {
|
||||
continue;
|
||||
}
|
||||
item = generatePopupItem(menu);
|
||||
item.addEventListener("mousedown", (e) => callback(menu.id));
|
||||
// item.onclick = (e) => callback(menu.id);
|
||||
break;
|
||||
case "popup":
|
||||
item = generatePopupItem(menu);
|
||||
const subMenu = generatePopupDiv(menu.popup, callback);
|
||||
item.appendChild(subMenu);
|
||||
break;
|
||||
case "separator":
|
||||
item = document.createElement("hr");
|
||||
break;
|
||||
}
|
||||
root.appendChild(item);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
//? one row of popup
|
||||
function generatePopupItem(menu: IMenuItem | IMenuPopup): HTMLElement {
|
||||
const item = document.createElement("li");
|
||||
|
||||
//? checkmark
|
||||
const checkMark = document.createElement("span");
|
||||
checkMark.classList.add("checkmark");
|
||||
checkMark.textContent = menu.checked ? "✓" : " ";
|
||||
item.appendChild(checkMark);
|
||||
|
||||
//? display text
|
||||
// @ts-ignore
|
||||
// const [caption, keystroke] = menu.caption.split('\t')
|
||||
const label = generateCaption(menu.caption);
|
||||
label.classList.add("caption");
|
||||
item.appendChild(label);
|
||||
|
||||
//? keystroke
|
||||
|
||||
const shortcut = document.createElement("span");
|
||||
shortcut.classList.add("keystroke");
|
||||
shortcut.textContent = menu.type == "menuitem" ? menu.shortcut : "";
|
||||
item.appendChild(shortcut);
|
||||
|
||||
//? sub-menu sign
|
||||
const chevron = document.createElement("span");
|
||||
chevron.classList.add("chevron");
|
||||
chevron.textContent = menu.type == "popup" ? "🞂" : " ";
|
||||
item.appendChild(chevron);
|
||||
// item.textContent = `${menu.checked? '✓' : ' '} ${menu.caption}`;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function generateCaption(caption: string): HTMLElement {
|
||||
const regex = /(&(\w))/gm;
|
||||
const subst = `<u>$2</u>`;
|
||||
|
||||
// The substituted value will be contained in the result variable
|
||||
caption = caption.replace(regex, subst);
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.classList.add("caption");
|
||||
span.innerHTML = caption;
|
||||
return span;
|
||||
}
|
||||
|
|
@ -119,29 +119,31 @@ export default abstract class Movable extends GuiObj {
|
|||
CURSOR,
|
||||
CURSOR
|
||||
);
|
||||
const startX = downEvent.clientX;
|
||||
const startY = downEvent.clientY;
|
||||
const startX = downEvent.pageX;
|
||||
const startY = downEvent.pageY;
|
||||
|
||||
const handleMove = (moveEvent: MouseEvent) => {
|
||||
const newMouseX = moveEvent.clientX;
|
||||
const newMouseY = moveEvent.clientY;
|
||||
const newMouseX = moveEvent.pageX;
|
||||
const newMouseY = moveEvent.pageY;
|
||||
const deltaY = newMouseY - startY;
|
||||
const deltaX = newMouseX - startX;
|
||||
layout.setResizing("move", deltaX, deltaY);
|
||||
};
|
||||
|
||||
const trottledMove = throttle(handleMove, 5);
|
||||
|
||||
const handleMouseUp = (upEvent: MouseEvent) => {
|
||||
upEvent.stopPropagation();
|
||||
if (upEvent.button != 0) return; // only care LeftButton
|
||||
document.removeEventListener("mousemove", handleMove);
|
||||
document.removeEventListener("mousemove", trottledMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
const newMouseX = upEvent.clientX;
|
||||
const newMouseY = upEvent.clientY;
|
||||
const newMouseX = upEvent.pageX;
|
||||
const newMouseY = upEvent.pageY;
|
||||
const deltaY = newMouseY - startY;
|
||||
const deltaX = newMouseX - startX;
|
||||
layout.setResizing("final", deltaX, deltaY);
|
||||
};
|
||||
document.addEventListener("mousemove", throttle(handleMove, 75));
|
||||
document.addEventListener("mousemove", trottledMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
|
|
@ -165,29 +167,32 @@ export default abstract class Movable extends GuiObj {
|
|||
if (downEvent.button != 0) return; // only care LeftButton
|
||||
const layout = this.getparentlayout() as Layout;
|
||||
layout.setMoving("start", 0, 0);
|
||||
const startX = downEvent.clientX;
|
||||
const startY = downEvent.clientY;
|
||||
const startX = downEvent.pageX;
|
||||
const startY = downEvent.pageY;
|
||||
|
||||
const handleMove = (moveEvent: MouseEvent) => {
|
||||
const newMouseX = moveEvent.clientX;
|
||||
const newMouseY = moveEvent.clientY;
|
||||
const newMouseX = moveEvent.pageX;
|
||||
const newMouseY = moveEvent.pageY;
|
||||
const deltaY = newMouseY - startY;
|
||||
const deltaX = newMouseX - startX;
|
||||
// console.log("move", deltaX, deltaY);
|
||||
layout.setMoving("move", deltaX, deltaY);
|
||||
};
|
||||
|
||||
const trottledMove = throttle(handleMove, 5);
|
||||
|
||||
const handleMouseUp = (upEvent: MouseEvent) => {
|
||||
if (upEvent.button != 0) return; // only care LeftButton
|
||||
upEvent.stopPropagation();
|
||||
document.removeEventListener("mousemove", handleMove);
|
||||
document.removeEventListener("mousemove", trottledMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
const newMouseX = upEvent.clientX;
|
||||
const newMouseY = upEvent.clientY;
|
||||
const newMouseX = upEvent.pageX;
|
||||
const newMouseY = upEvent.pageY;
|
||||
const deltaY = newMouseY - startY;
|
||||
const deltaX = newMouseX - startX;
|
||||
layout.setMoving("final", deltaX, deltaY);
|
||||
};
|
||||
document.addEventListener("mousemove", throttle(handleMove, 10));
|
||||
document.addEventListener("mousemove", trottledMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,36 @@
|
|||
import BaseObject from "./BaseObject";
|
||||
import { assume } from "../../utils";
|
||||
import { assume, px } from "../../utils";
|
||||
import {
|
||||
MenuItem,
|
||||
IPopupMenu,
|
||||
generatePopupDiv,
|
||||
extractCaption,
|
||||
ICLoseablePopup,
|
||||
destroyActivePopup,
|
||||
setActivePopup,
|
||||
deactivePopup,
|
||||
IMenuItem,
|
||||
} from "./MenuItem";
|
||||
import { Skin, UIRoot } from "../../UIRoot";
|
||||
import { registerAction } from "./menuWa5actions";
|
||||
// import { sleep } from 'deasync';
|
||||
// import { deasync } from '@kaciras/deasync';
|
||||
// import sp from 'synchronized-promise';
|
||||
|
||||
// taken from sp test
|
||||
const asyncFunctionBuilder =
|
||||
(success) =>
|
||||
(value, timeouts = 1000) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(function () {
|
||||
if (success) {
|
||||
resolve(value);
|
||||
} else {
|
||||
reject(new TypeError(value));
|
||||
}
|
||||
}, timeouts);
|
||||
});
|
||||
};
|
||||
// const asyncFunctionBuilder =
|
||||
// (success) =>
|
||||
// (value, timeouts = 1000) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// setTimeout(function () {
|
||||
// if (success) {
|
||||
// resolve(value);
|
||||
// } else {
|
||||
// reject(new TypeError(value));
|
||||
// }
|
||||
// }, timeouts);
|
||||
// });
|
||||
// };
|
||||
// const async_sleep = (timeout) => {
|
||||
// // setTimeout(() => done(null, "wake up!"), timeout);
|
||||
// const done = () => {}
|
||||
|
|
@ -26,148 +39,103 @@ const asyncFunctionBuilder =
|
|||
// const sleep = sp(async_sleep)
|
||||
// const sleep = sp(asyncFunctionBuilder(true))
|
||||
|
||||
export type MenuItem =
|
||||
| {
|
||||
type: "menuitem";
|
||||
caption: string;
|
||||
id: number;
|
||||
checked: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
| { type: "separator" }
|
||||
| {
|
||||
type: "popup";
|
||||
caption: string;
|
||||
popup: PopupMenu;
|
||||
disabled?: boolean;
|
||||
children?: MenuItem[];
|
||||
};
|
||||
function waitPopup(popup: PopupMenu, x = 0, y = 0): Promise<number> {
|
||||
// const closePopup = () => div.remove();
|
||||
|
||||
function waitPopup(popup: PopupMenu): Promise<number> {
|
||||
// const closePopup = () => div.remove();
|
||||
|
||||
// https://stackoverflow.com/questions/54916739/wait-for-click-event-inside-a-for-loop-similar-to-prompt
|
||||
return new Promise(acc => {
|
||||
// https://stackoverflow.com/questions/54916739/wait-for-click-event-inside-a-for-loop-similar-to-prompt
|
||||
return new Promise((acc) => {
|
||||
// let result: number = -1;
|
||||
const itemClick = (id: number) => {
|
||||
closePopup()
|
||||
closePopup();
|
||||
// result = id;
|
||||
acc(id);
|
||||
};
|
||||
const div = generatePopupDiv(popup, itemClick);
|
||||
if (x || y) {
|
||||
div.style.left = px(x);
|
||||
div.style.top = px(y);
|
||||
}
|
||||
document.getElementById("web-amp").appendChild(div);
|
||||
const closePopup = () => div.remove()
|
||||
const closePopup = () => {
|
||||
div.remove();
|
||||
popup._successPromise = null;
|
||||
};
|
||||
const outsideClick = (ret: number) => {
|
||||
closePopup();
|
||||
acc(ret);
|
||||
};
|
||||
popup._successPromise = outsideClick;
|
||||
|
||||
function handleClick() {
|
||||
document.removeEventListener('click', handleClick);
|
||||
closePopup()
|
||||
document.removeEventListener("click", handleClick);
|
||||
closePopup();
|
||||
acc(-1);
|
||||
}
|
||||
document.addEventListener('click', handleClick);
|
||||
document.addEventListener("click", handleClick);
|
||||
});
|
||||
// return 1;
|
||||
}
|
||||
|
||||
export function generatePopupDiv(popup: PopupMenu, callback: Function): HTMLElement {
|
||||
const root = document.createElement("ul");
|
||||
root.className = 'popup-menu-container'
|
||||
// root.style.zIndex = "1000";
|
||||
// console.log('generating popup:', popup)
|
||||
for (const menu of popup._items) {
|
||||
// const menuitem = document.createElement("li");
|
||||
let item: HTMLElement;
|
||||
// root.appendChild(item);
|
||||
switch (menu.type) {
|
||||
case "menuitem":
|
||||
item = generatePopupItem(menu);
|
||||
item.onclick = (e) => callback(menu.id);
|
||||
break;
|
||||
case "popup":
|
||||
item = generatePopupItem(menu);
|
||||
const subMenu = generatePopupDiv(menu.popup, callback);
|
||||
item.appendChild(subMenu)
|
||||
break;
|
||||
case "separator":
|
||||
item = document.createElement("hr");
|
||||
break;
|
||||
}
|
||||
root.appendChild(item);
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
//? one row of popup
|
||||
function generatePopupItem(menu: MenuItem): HTMLElement {
|
||||
const item = document.createElement("li");
|
||||
|
||||
//? checkmark
|
||||
const checkMark = document.createElement("span");
|
||||
checkMark.classList.add('checkmark')
|
||||
checkMark.textContent = menu.checked? '✓' : ' ';
|
||||
item.appendChild(checkMark)
|
||||
|
||||
//? display text
|
||||
const [caption, keystroke] = menu.caption.split('\t')
|
||||
const label = generateCaption(caption);
|
||||
label.classList.add('caption')
|
||||
item.appendChild(label)
|
||||
|
||||
//? keystroke
|
||||
const shortcut = document.createElement("span");
|
||||
shortcut.classList.add('keystroke')
|
||||
shortcut.textContent = keystroke;
|
||||
item.appendChild(shortcut)
|
||||
|
||||
//? sub-menu sign
|
||||
const chevron = document.createElement("span");
|
||||
chevron.classList.add('chevron')
|
||||
chevron.textContent = menu.type=='popup'? '⮀' : ' ';
|
||||
item.appendChild(chevron)
|
||||
// item.textContent = `${menu.checked? '✓' : ' '} ${menu.caption}`;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
function generateCaption(caption: string): HTMLElement {
|
||||
const regex = /(&(\w))/gm;
|
||||
const subst = `<u>$2</u>`;
|
||||
|
||||
// The substituted value will be contained in the result variable
|
||||
caption = caption.replace(regex, subst);
|
||||
|
||||
const span = document.createElement("span");
|
||||
span.classList.add('caption')
|
||||
span.innerHTML = caption;
|
||||
return span
|
||||
}
|
||||
|
||||
export default class PopupMenu extends BaseObject {
|
||||
export default class PopupMenu
|
||||
extends BaseObject
|
||||
implements IPopupMenu, ICLoseablePopup
|
||||
{
|
||||
static GUID = "f4787af44ef7b2bb4be7fb9c8da8bea9";
|
||||
_items: MenuItem[] = [];
|
||||
children: MenuItem[] = [];
|
||||
_uiRoot: UIRoot;
|
||||
|
||||
constructor(uiRoot: UIRoot) {
|
||||
super();
|
||||
this._uiRoot = uiRoot;
|
||||
|
||||
// this._div = document.createElement(
|
||||
// this.getElTag().toLowerCase().replace("_", "")
|
||||
// );
|
||||
}
|
||||
private _addcommand(
|
||||
cmdText: string,
|
||||
cmd_id: number,
|
||||
checked: boolean = false,
|
||||
disabled: boolean = false,
|
||||
data: { [key: string]: any } = {}
|
||||
) {
|
||||
this.children.push({
|
||||
type: "menuitem",
|
||||
// caption: cmdText,
|
||||
...extractCaption(cmdText),
|
||||
id: cmd_id,
|
||||
checked,
|
||||
disabled,
|
||||
data,
|
||||
});
|
||||
}
|
||||
addcommand(
|
||||
cmdText: string,
|
||||
cmd_id: number,
|
||||
checked: boolean,
|
||||
disabled: boolean
|
||||
) {
|
||||
this._items.push({
|
||||
type: "menuitem",
|
||||
caption: cmdText,
|
||||
id: cmd_id,
|
||||
checked,
|
||||
disabled,
|
||||
});
|
||||
if (cmd_id == 32767) {
|
||||
this._loadSkins();
|
||||
return;
|
||||
}
|
||||
this._addcommand(cmdText, cmd_id, checked, disabled);
|
||||
}
|
||||
addseparator() {
|
||||
this._items.push({ type: "separator" });
|
||||
this.children.push({ type: "separator" });
|
||||
}
|
||||
addsubmenu(popup: PopupMenu, submenutext: string) {
|
||||
this._items.push({ type: "popup", popup: popup, caption: submenutext });
|
||||
// this.children.push({ type: "popup", popup: popup, caption: submenutext });
|
||||
this.children.push({
|
||||
type: "popup",
|
||||
popup: popup,
|
||||
...extractCaption(submenutext),
|
||||
});
|
||||
// // TODO:
|
||||
// this.addcommand(submenutext, 0, false, false)
|
||||
}
|
||||
checkcommand(cmd_id: number, check: boolean) {
|
||||
const item = this._items.find((item) => {
|
||||
const item = this.children.find((item) => {
|
||||
return item.type === "menuitem" && item.id === cmd_id;
|
||||
});
|
||||
assume(item != null, `Could not find item with id "${cmd_id}"`);
|
||||
|
|
@ -177,25 +145,72 @@ export default class PopupMenu extends BaseObject {
|
|||
item.checked = check;
|
||||
}
|
||||
disablecommand(cmd_id: number, disable: boolean) {
|
||||
for (const item of this._items) {
|
||||
for (const item of this.children) {
|
||||
if (item.type == "menuitem" && item.id == cmd_id) {
|
||||
item.disabled = disable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async popatmouse(): Promise<number> {
|
||||
console.log('popAtMouse.start...:')
|
||||
const result = await waitPopup(this)
|
||||
console.log('popAtMouse.return:', result)
|
||||
console.log("popAtMouse.start...:");
|
||||
const mousePos = this._uiRoot._mousePos;
|
||||
// const result = await waitPopup(this, mousePos.x, mousePos.y)
|
||||
const result = await this.popatxy(mousePos.x, mousePos.y);
|
||||
console.log("popAtMouse.return:", result);
|
||||
return result;
|
||||
}
|
||||
async popatxy(x:number, y:number):Promise<number>{
|
||||
return await waitPopup(this)
|
||||
async popatxy(x: number, y: number): Promise<number> {
|
||||
destroyActivePopup();
|
||||
setActivePopup(this);
|
||||
const ret = await waitPopup(this, x, y);
|
||||
deactivePopup(this);
|
||||
// setActivePopup(this)
|
||||
// this._showButton(this._elDown);
|
||||
// this._div.classList.add("open");
|
||||
|
||||
// ACTIVE_MENU = this;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* called by such Menu to close this pupup in favour of
|
||||
* that Menu want to show their own popup (user click that Menu)
|
||||
*/
|
||||
doClosePopup() {
|
||||
if (this._successPromise) {
|
||||
this._successPromise(-1);
|
||||
}
|
||||
}
|
||||
_successPromise: Function = null;
|
||||
|
||||
_loadSkins() {
|
||||
let action_id = 32767;
|
||||
this._uiRoot._skins.forEach((skin) => {
|
||||
const name = typeof skin === "string" ? skin : skin.name;
|
||||
const url = typeof skin === "string" ? skin : skin.url;
|
||||
const skin_info: Skin = { name, url };
|
||||
action_id++;
|
||||
|
||||
registerAction(action_id, {
|
||||
//? Skin, checked or not
|
||||
onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {
|
||||
menu.checked =
|
||||
uiRoot.getSkinName() == menu.caption ||
|
||||
uiRoot.getSkinUrl() == menu.data.url;
|
||||
},
|
||||
onExecute: (uiRoot: UIRoot) => {
|
||||
uiRoot.switchSkin(skin_info);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
this._addcommand(name, action_id, false, false, skin_info);
|
||||
});
|
||||
}
|
||||
|
||||
// popatmouse(): number {
|
||||
// const message = this._items.map((item) => {
|
||||
// const message = this.children.map((item) => {
|
||||
// switch (item.type) {
|
||||
// case "separator":
|
||||
// return "------";
|
||||
|
|
@ -206,7 +221,7 @@ export default class PopupMenu extends BaseObject {
|
|||
// message.unshift("Pick the number matching your choice:\n");
|
||||
// let choice: number | null = null;
|
||||
// while (
|
||||
// !this._items.some((item) => item.type === "item" && item.id === choice)
|
||||
// !this.children.some((item) => item.type === "item" && item.id === choice)
|
||||
// ) {
|
||||
// choice = Number(window.prompt(message.join("\n")));
|
||||
// if (choice == 0) break;
|
||||
|
|
@ -219,6 +234,15 @@ export default class PopupMenu extends BaseObject {
|
|||
// return this.popatmouse();
|
||||
// }
|
||||
getnumcommands() {
|
||||
return this._items.length;
|
||||
return this.children.length;
|
||||
}
|
||||
|
||||
hideMenu(cmd_id: number) {
|
||||
for (const item of this.children) {
|
||||
if (item.type == "menuitem" && item.id == cmd_id) {
|
||||
item.invisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -622,7 +622,7 @@ export default class SystemObject extends BaseObject {
|
|||
*/
|
||||
newdynamiccontainer(container_id: string): Container {
|
||||
//TODO
|
||||
return unimplemented(null)
|
||||
return unimplemented(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ export default class Text extends GuiObj {
|
|||
case "default":
|
||||
// (str) A static string to be displayed.
|
||||
// console.log('THETEXT', value)
|
||||
// if (value.startsWith(':')) {
|
||||
// value = this._interpolateText(value)
|
||||
// }
|
||||
this._text = value;
|
||||
this._renderText();
|
||||
break;
|
||||
|
|
@ -348,6 +351,24 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
|
|||
]);
|
||||
}
|
||||
|
||||
_interpolateText(value: string): string {
|
||||
switch (value.toLowerCase()) {
|
||||
case ":componentname":
|
||||
const layout = this.getparentlayout();
|
||||
if (layout) {
|
||||
//debugger
|
||||
try {
|
||||
// sometime error with wasabi
|
||||
return layout.getcontainer()._name || value;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
gettext(): string {
|
||||
if (this._alternateText) {
|
||||
// alternate text is used in Winamp3 to show a hint of a Play button while mouse down.
|
||||
|
|
@ -542,7 +563,7 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
|
|||
|
||||
_getBitmapFontTextWidth(font: BitmapFont): number {
|
||||
const charWidth = font._charWidth;
|
||||
return this.gettext().length * charWidth + this._paddingX * 2;
|
||||
return this.gettext().length * charWidth;
|
||||
}
|
||||
|
||||
_getTrueTypeTextWidth(font: TrueTypeFont): number {
|
||||
|
|
@ -554,14 +575,26 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x
|
|||
*
|
||||
* @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
|
||||
*/
|
||||
const self = this;
|
||||
// const self = this;
|
||||
let txt = this.gettext();
|
||||
if (this._forceuppercase) {
|
||||
txt = txt.toUpperCase();
|
||||
} else if (this._forcelowercase) {
|
||||
txt = txt.toLowerCase();
|
||||
}
|
||||
const fontFamily =
|
||||
(font && font.getFontFamily()) ||
|
||||
'"Liberation Sans", "DejaVu Sans", Arial';
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
const context = canvas.getContext("2d");
|
||||
context.font = `${this._fontSize || 11}px ${
|
||||
(font && font.getFontFamily()) || "Arial"
|
||||
}`;
|
||||
context.font = `${this._bold ? "700" : ""} ${
|
||||
this._fontSize || 11
|
||||
}px ${fontFamily}`;
|
||||
|
||||
const metrics = context.measureText(this.gettext());
|
||||
return metrics.width + self._paddingX * 2;
|
||||
|
||||
return Math.ceil(metrics.width /*+ self._paddingX * 2*/);
|
||||
}
|
||||
|
||||
draw() {
|
||||
|
|
|
|||
|
|
@ -21,23 +21,23 @@ export class VisPaintHandler {
|
|||
* Attemp to build cached bitmaps for later use while render a frame.
|
||||
* Purpose: fast rendering in animation loop
|
||||
*/
|
||||
prepare() { }
|
||||
prepare() {}
|
||||
|
||||
/**
|
||||
* Called once per frame rendiring
|
||||
*/
|
||||
paintFrame() { }
|
||||
paintFrame() {}
|
||||
|
||||
/**
|
||||
* Attemp to cleanup cached bitmaps
|
||||
*/
|
||||
dispose() { }
|
||||
dispose() {}
|
||||
|
||||
/**
|
||||
* called if it is an AVS.
|
||||
* @param action vis_prev | vis_next | vis_f5 (fullscreen) |
|
||||
*/
|
||||
doAction(action: string, param: string) { }
|
||||
doAction(action: string, param: string) {}
|
||||
}
|
||||
|
||||
// type VisPaintHandlerClass = {new(vis: Vis): VisPaintHandler;};
|
||||
|
|
@ -721,15 +721,19 @@ class WavePaintHandler extends VisPaintHandler {
|
|||
}
|
||||
|
||||
if (using16temporaryCanvas) {
|
||||
const canvas = this._vis._canvas
|
||||
const visCtx = canvas.getContext('2d');
|
||||
const canvas = this._vis._canvas;
|
||||
const visCtx = canvas.getContext("2d");
|
||||
visCtx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
visCtx.drawImage(
|
||||
this._16h,
|
||||
0, 0, // sx,sy
|
||||
72, 16, // sw,sh
|
||||
0, 0, //dx,dy
|
||||
canvas.width, canvas.height //dw,dh
|
||||
0,
|
||||
0, // sx,sy
|
||||
72,
|
||||
16, // sw,sh
|
||||
0,
|
||||
0, //dx,dy
|
||||
canvas.width,
|
||||
canvas.height //dw,dh
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -804,10 +808,14 @@ class WavePaintHandler extends VisPaintHandler {
|
|||
for (y = top; y <= bottom; y++) {
|
||||
this._ctx.drawImage(
|
||||
this._bar,
|
||||
0, colorIndex, // sx,sy
|
||||
1, 1, // sw,sh
|
||||
x, y, //dx,dy
|
||||
1, 1 //dw,dh
|
||||
0,
|
||||
colorIndex, // sx,sy
|
||||
1,
|
||||
1, // sw,sh
|
||||
x,
|
||||
y, //dx,dy
|
||||
1,
|
||||
1 //dw,dh
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -815,10 +823,14 @@ class WavePaintHandler extends VisPaintHandler {
|
|||
paintWavDot(x: number, y: number, colorIndex: number) {
|
||||
this._ctx.drawImage(
|
||||
this._bar,
|
||||
0, colorIndex, // sx,sy
|
||||
1, 1, // sw,sh
|
||||
x, y, //dx,dy
|
||||
1, 1 //dw,dh
|
||||
0,
|
||||
colorIndex, // sx,sy
|
||||
1,
|
||||
1, // sw,sh
|
||||
x,
|
||||
y, //dx,dy
|
||||
1,
|
||||
1 //dw,dh
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -835,10 +847,14 @@ class WavePaintHandler extends VisPaintHandler {
|
|||
for (y = top; y <= bottom; y++) {
|
||||
this._ctx.drawImage(
|
||||
this._bar,
|
||||
0, colorIndex, // sx,sy
|
||||
1, 1, // sw,sh
|
||||
x, y, //dx,dy
|
||||
1, 1 //dw,dh
|
||||
0,
|
||||
colorIndex, // sx,sy
|
||||
1,
|
||||
1, // sw,sh
|
||||
x,
|
||||
y, //dx,dy
|
||||
1,
|
||||
1 //dw,dh
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,217 +1,243 @@
|
|||
import PopupMenu, { MenuItem } from "./PopupMenu";
|
||||
import { UIRoot } from "../../UIRoot";
|
||||
import PopupMenu from "./PopupMenu";
|
||||
import { MenuItem } from "./MenuItem";
|
||||
|
||||
export function getWa5Popup(popupId: string): PopupMenu {
|
||||
if(['PE_Help', 'ML_Help'].includes(popupId)) popupId = 'Help';
|
||||
const res = wa5commonRes.includes(popupId) ? wa5commonRes : wa5miscRes.includes(popupId) ? popupId : '';
|
||||
// const popupJson =getPopupJson(popupId, res);
|
||||
// console.log('FOUND', popupId, popupJson)
|
||||
const popup =getPopupMenu(popupId, res);
|
||||
console.log('FOUND', popupId, popup)
|
||||
return popup;
|
||||
export function getWa5Popup(popupId: string, uiRoot: UIRoot): PopupMenu {
|
||||
if (["PE_Help", "ML_Help"].includes(popupId)) popupId = "Help";
|
||||
else if (popupId.toLowerCase() == "presets") popupId = "EQpresets";
|
||||
const id = `POPUP "${popupId}"`;
|
||||
const res = wa5commonRes.includes(id)
|
||||
? wa5commonRes
|
||||
: wa5miscRes.includes(id)
|
||||
? wa5miscRes
|
||||
: wa5controlRes;
|
||||
// const res = wa5commonRes.includes(popupId) ? wa5commonRes : wa5miscRes.includes(popupId) ? popupId : '';
|
||||
const popup = getPopupJson(popupId, res, uiRoot);
|
||||
// console.log('FOUND', popupId, popupJson)
|
||||
// const popup =getPopupMenu(popupId, res);
|
||||
console.log("FOUND", popupId, popup);
|
||||
return popup;
|
||||
}
|
||||
|
||||
function getPopupJson(popupId: string, res:string): MenuItem[] {
|
||||
const root = [];
|
||||
let container = root;
|
||||
let levelStack = [root];
|
||||
let popup: PopupMenu = null;
|
||||
let popupStack: PopupMenu[] = [];
|
||||
let found = false;
|
||||
// let currentItem = null
|
||||
function getPopupJson(popupId: string, res: string, uiRoot: UIRoot): PopupMenu {
|
||||
let root: PopupMenu;
|
||||
// let container = root;
|
||||
// let levelStack = [root];
|
||||
let popup: PopupMenu = null;
|
||||
let popupStack: PopupMenu[] = [];
|
||||
let found = false;
|
||||
// let currentItem = null
|
||||
|
||||
// Looping setiap baris pada string menu
|
||||
for (let line of res.split('\n')) {
|
||||
line = line.trim()
|
||||
// Looping setiap baris pada string menu
|
||||
for (let line of res.split("\n")) {
|
||||
line = line.trim();
|
||||
|
||||
if(!found && !line.startsWith('POPUP')) // skip until found popup
|
||||
continue;
|
||||
if (!found && !line.startsWith("POPUP"))
|
||||
// skip until found popup
|
||||
continue;
|
||||
|
||||
// Mengabaikan baris yang tidak penting
|
||||
if (!line || line.startsWith('//')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mengambil level pada baris saat ini
|
||||
// const level = line.search(/\S/);
|
||||
|
||||
// Mengecek apakah baris merupakan menu
|
||||
// const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i);
|
||||
const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i);
|
||||
if (menuMatch) {
|
||||
// console.log('match', menuMatch)
|
||||
// Mengambil informasi menu
|
||||
let [, tag, t1, t2, sid, flags] = menuMatch;
|
||||
const type = tag == 'POPUP' ? 'popup' : (t1 == 'SEPARATOR' || (flags || '').indexOf('MFT_SEPARATOR') >= 0) ? 'separator' : 'menuitem';
|
||||
const id = parseInt(sid)
|
||||
|
||||
|
||||
if(!found) {
|
||||
if(type=='popup' && t2 == popupId) {
|
||||
found = true;
|
||||
} else {
|
||||
continue; // skip
|
||||
}
|
||||
}
|
||||
|
||||
flags = flags || ''
|
||||
// Membuat objek menu baru
|
||||
// @ts-ignore
|
||||
const menu: MenuItem = {
|
||||
type,
|
||||
};
|
||||
container.push(menu); // attach to prent
|
||||
switch (menu.type) {
|
||||
case 'popup':
|
||||
const newPopup = new PopupMenu();
|
||||
if(popup){ // if it is a sub-popup, attach to parent
|
||||
popup.addsubmenu(newPopup, t2)
|
||||
}
|
||||
popup = newPopup;
|
||||
popupStack.push(popup);
|
||||
menu.popup = popup;
|
||||
menu.caption = t2;
|
||||
menu.children = [];
|
||||
container = menu.children;
|
||||
if (flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
levelStack.push(container)
|
||||
break;
|
||||
case 'menuitem':
|
||||
menu.caption = t2;
|
||||
menu.id = id;
|
||||
// if(flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
menu.disabled = flags.indexOf('GRAYED') >= 0;
|
||||
popup.addcommand(t2, id, false, menu.disabled)
|
||||
break;
|
||||
case 'separator':
|
||||
popup.addseparator()
|
||||
break;
|
||||
}
|
||||
// const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid);
|
||||
|
||||
// console.log('m', newMenu, '>>', flags)
|
||||
// @ts-ignore
|
||||
// menu.flags = flags;
|
||||
|
||||
// console.log('m', menu)
|
||||
|
||||
} else if (['}', 'END'].includes(line.trim())) {
|
||||
// Menutup menu saat ini
|
||||
levelStack.pop();
|
||||
container = levelStack[levelStack.length - 1];
|
||||
if(found) {
|
||||
if(container == root)
|
||||
break;
|
||||
|
||||
popupStack.pop();
|
||||
popup = popupStack[popupStack.length - 1];
|
||||
}
|
||||
}
|
||||
// Mengabaikan baris yang tidak penting
|
||||
if (!line || line.startsWith("//")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return root;
|
||||
// Mengambil level pada baris saat ini
|
||||
// const level = line.search(/\S/);
|
||||
|
||||
// Mengecek apakah baris merupakan menu
|
||||
// const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i);
|
||||
const menuMatch = line.match(
|
||||
/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i
|
||||
);
|
||||
if (menuMatch) {
|
||||
// console.log('match', menuMatch)
|
||||
// Mengambil informasi menu
|
||||
let [, tag, t1, t2, sid, flags] = menuMatch;
|
||||
const type =
|
||||
tag == "POPUP"
|
||||
? "popup"
|
||||
: t1 == "SEPARATOR" || (flags || "").indexOf("MFT_SEPARATOR") >= 0
|
||||
? "separator"
|
||||
: "menuitem";
|
||||
const id = parseInt(sid);
|
||||
|
||||
if (!found) {
|
||||
if (type == "popup" && t2 == popupId) {
|
||||
found = true;
|
||||
} else {
|
||||
continue; // skip
|
||||
}
|
||||
}
|
||||
|
||||
flags = flags || "";
|
||||
// Membuat objek menu baru
|
||||
// @ts-ignore
|
||||
const menu: MenuItem = {
|
||||
type,
|
||||
};
|
||||
// container.push(menu); // attach to prent
|
||||
switch (menu.type) {
|
||||
case "popup":
|
||||
const newPopup = new PopupMenu(uiRoot);
|
||||
if (popup) {
|
||||
// if it is a sub-popup, attach to parent
|
||||
popup.addsubmenu(newPopup, t2);
|
||||
} else {
|
||||
root = newPopup;
|
||||
}
|
||||
popup = newPopup;
|
||||
popupStack.push(popup);
|
||||
menu.popup = popup;
|
||||
menu.caption = t2;
|
||||
// menu.children = [];
|
||||
// container = menu.children;
|
||||
if (flags.indexOf("GRAYED") >= 0) menu.disabled = true;
|
||||
// levelStack.push(container)
|
||||
break;
|
||||
case "menuitem":
|
||||
menu.caption = t2;
|
||||
menu.id = id;
|
||||
// if(flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
menu.disabled = flags.indexOf("GRAYED") >= 0;
|
||||
popup.addcommand(t2, id, false, menu.disabled);
|
||||
break;
|
||||
case "separator":
|
||||
popup.addseparator();
|
||||
break;
|
||||
}
|
||||
// const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid);
|
||||
|
||||
// console.log('m', newMenu, '>>', flags)
|
||||
// @ts-ignore
|
||||
// menu.flags = flags;
|
||||
|
||||
// console.log('m', menu)
|
||||
} else if (["}", "END"].includes(line.trim())) {
|
||||
// Menutup menu saat ini
|
||||
// levelStack.pop();
|
||||
// container = levelStack[levelStack.length - 1];
|
||||
if (found) {
|
||||
if (popup == root) break;
|
||||
|
||||
popupStack.pop();
|
||||
popup = popupStack[popupStack.length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
function getPopupMenu(popupId: string, res:string): PopupMenu {
|
||||
let root: PopupMenu;
|
||||
// let container = root;
|
||||
// let levelStack = [root];
|
||||
let popup: PopupMenu = null;
|
||||
let popupStack: PopupMenu[] = [];
|
||||
let found = false;
|
||||
// let currentItem = null
|
||||
function getPopupMenu(popupId: string, res: string, uiRoot: UIRoot): PopupMenu {
|
||||
let root: PopupMenu;
|
||||
// let container = root;
|
||||
// let levelStack = [root];
|
||||
let popup: PopupMenu = null;
|
||||
let popupStack: PopupMenu[] = [];
|
||||
let found = false;
|
||||
// let currentItem = null
|
||||
|
||||
// Looping setiap baris pada string menu
|
||||
for (let line of res.split('\n')) {
|
||||
line = line.trim()
|
||||
// Looping setiap baris pada string menu
|
||||
for (let line of res.split("\n")) {
|
||||
line = line.trim();
|
||||
|
||||
if(!found && !line.startsWith('POPUP')) // skip until found popup
|
||||
continue;
|
||||
if (!found && !line.startsWith("POPUP"))
|
||||
// skip until found popup
|
||||
continue;
|
||||
|
||||
// Mengabaikan baris yang tidak penting
|
||||
if (!line || line.startsWith('//')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mengambil level pada baris saat ini
|
||||
// const level = line.search(/\S/);
|
||||
|
||||
// Mengecek apakah baris merupakan menu
|
||||
// const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i);
|
||||
const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i);
|
||||
if (menuMatch) {
|
||||
// console.log('match', menuMatch)
|
||||
// Mengambil informasi menu
|
||||
let [, tag, t1, t2, sid, flags] = menuMatch;
|
||||
const type = tag == 'POPUP' ? 'popup' : (t1 == 'SEPARATOR' || (flags || '').indexOf('MFT_SEPARATOR') >= 0) ? 'separator' : 'menuitem';
|
||||
const id = parseInt(sid)
|
||||
|
||||
|
||||
if(!found) {
|
||||
if(type=='popup' && t2 == popupId) {
|
||||
found = true;
|
||||
} else {
|
||||
continue; // skip
|
||||
}
|
||||
}
|
||||
|
||||
flags = flags || ''
|
||||
// Membuat objek menu baru
|
||||
// @ts-ignore
|
||||
const menu: MenuItem = {
|
||||
type,
|
||||
};
|
||||
// container.push(menu); // attach to prent
|
||||
switch (menu.type) {
|
||||
case 'popup':
|
||||
const newPopup = new PopupMenu();
|
||||
if(popup){ // if it is a sub-popup, attach to parent
|
||||
popup.addsubmenu(newPopup, t2)
|
||||
} else {
|
||||
root = newPopup
|
||||
}
|
||||
popup = newPopup;
|
||||
popupStack.push(popup);
|
||||
menu.popup = popup;
|
||||
menu.caption = t2;
|
||||
menu.children = [];
|
||||
// container = menu.children;
|
||||
if (flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
// levelStack.push(container)
|
||||
break;
|
||||
case 'menuitem':
|
||||
menu.caption = t2;
|
||||
menu.id = id;
|
||||
// if(flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
menu.disabled = flags.indexOf('GRAYED') >= 0;
|
||||
popup.addcommand(t2, id, false, menu.disabled)
|
||||
break;
|
||||
case 'separator':
|
||||
popup.addseparator()
|
||||
break;
|
||||
}
|
||||
// const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid);
|
||||
|
||||
// console.log('m', newMenu, '>>', flags)
|
||||
// @ts-ignore
|
||||
// menu.flags = flags;
|
||||
|
||||
// console.log('m', menu)
|
||||
|
||||
} else if (['}', 'END'].includes(line.trim())) {
|
||||
// Menutup menu saat ini
|
||||
// levelStack.pop();
|
||||
// container = levelStack[levelStack.length - 1];
|
||||
if(found) {
|
||||
if(popup == root)
|
||||
break;
|
||||
|
||||
popupStack.pop();
|
||||
popup = popupStack[popupStack.length - 1];
|
||||
}
|
||||
}
|
||||
// Mengabaikan baris yang tidak penting
|
||||
if (!line || line.startsWith("//")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return root;
|
||||
// Mengambil level pada baris saat ini
|
||||
// const level = line.search(/\S/);
|
||||
|
||||
// Mengecek apakah baris merupakan menu
|
||||
// const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i);
|
||||
const menuMatch = line.match(
|
||||
/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i
|
||||
);
|
||||
if (menuMatch) {
|
||||
// console.log('match', menuMatch)
|
||||
// Mengambil informasi menu
|
||||
let [, tag, t1, t2, sid, flags] = menuMatch;
|
||||
const type =
|
||||
tag == "POPUP"
|
||||
? "popup"
|
||||
: t1 == "SEPARATOR" || (flags || "").indexOf("MFT_SEPARATOR") >= 0
|
||||
? "separator"
|
||||
: "menuitem";
|
||||
const id = parseInt(sid);
|
||||
|
||||
if (!found) {
|
||||
if (type == "popup" && t2 == popupId) {
|
||||
found = true;
|
||||
} else {
|
||||
continue; // skip
|
||||
}
|
||||
}
|
||||
|
||||
flags = flags || "";
|
||||
// Membuat objek menu baru
|
||||
// @ts-ignore
|
||||
const menu: MenuItem = {
|
||||
type,
|
||||
};
|
||||
// container.push(menu); // attach to prent
|
||||
switch (menu.type) {
|
||||
case "popup":
|
||||
const newPopup = new PopupMenu(uiRoot);
|
||||
if (popup) {
|
||||
// if it is a sub-popup, attach to parent
|
||||
popup.addsubmenu(newPopup, t2);
|
||||
} else {
|
||||
root = newPopup;
|
||||
}
|
||||
popup = newPopup;
|
||||
popupStack.push(popup);
|
||||
menu.popup = popup;
|
||||
menu.caption = t2;
|
||||
// menu.children = [];
|
||||
// container = menu.children;
|
||||
if (flags.indexOf("GRAYED") >= 0) menu.disabled = true;
|
||||
// levelStack.push(container)
|
||||
break;
|
||||
case "menuitem":
|
||||
menu.caption = t2;
|
||||
menu.id = id;
|
||||
// if(flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
menu.disabled = flags.indexOf("GRAYED") != -1;
|
||||
popup.addcommand(t2, id, false, menu.disabled);
|
||||
if (flags.indexOf("HIDDEN") != -1) {
|
||||
popup.hideMenu(id);
|
||||
}
|
||||
break;
|
||||
case "separator":
|
||||
popup.addseparator();
|
||||
break;
|
||||
}
|
||||
// const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid);
|
||||
|
||||
// console.log('m', newMenu, '>>', flags)
|
||||
// @ts-ignore
|
||||
// menu.flags = flags;
|
||||
|
||||
// console.log('m', menu)
|
||||
} else if (["}", "END"].includes(line.trim())) {
|
||||
// Menutup menu saat ini
|
||||
// levelStack.pop();
|
||||
// container = levelStack[levelStack.length - 1];
|
||||
if (found) {
|
||||
if (popup == root) break;
|
||||
|
||||
popupStack.pop();
|
||||
popup = popupStack[popupStack.length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
const wa5commonRes = `256 MENUEX
|
||||
|
|
@ -314,8 +340,9 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
}
|
||||
}
|
||||
MENUITEM "", 0, MFT_SEPARATOR, MFS_ENABLED
|
||||
MENUITEM "Time &elapsed Ctrl+T toggles", 40037, MFT_STRING, MFS_ENABLED
|
||||
MENUITEM "Time re&maining Ctrl+T toggles", 40038, MFT_STRING, MFS_ENABLED
|
||||
MENUITEM "&Time elapsed Alt+T toggles", 40037, MFT_STRING, MFS_ENABLED
|
||||
MENUITEM "Time re&maining Alt+T toggles", 40038, MFT_STRING, MFS_ENABLED
|
||||
MENUITEM "Time remaining toggle Alt+T", 40039, MFT_STRING, WEBAMP_HIDDEN
|
||||
MENUITEM "", 0, MFT_SEPARATOR, MFS_ENABLED
|
||||
MENUITEM "&Always On Top Ctrl+A", 40019, MFT_STRING, MFS_ENABLED
|
||||
MENUITEM "&Double Size Ctrl+D", 40165, MFT_STRING, MFS_ENABLED
|
||||
|
|
@ -721,4 +748,73 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`;
|
||||
|
||||
//from gen_ff.dll
|
||||
const wa5controlRes = `1281 MENU
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
{
|
||||
POPUP "ControlMenu"
|
||||
{
|
||||
POPUP "&Opacity"
|
||||
{
|
||||
MENUITEM "1&00%", 42211
|
||||
MENUITEM "&90%", 42210
|
||||
MENUITEM "&80%", 42209
|
||||
MENUITEM "&70%", 42208
|
||||
MENUITEM "&60%", 42207
|
||||
MENUITEM "&50%", 42206
|
||||
MENUITEM "&40%", 42205
|
||||
MENUITEM "&30%", 42204
|
||||
MENUITEM "&20%", 42203
|
||||
MENUITEM "&10%", 42202
|
||||
MENUITEM "&Custom", 42227
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Opaque on &Focus", 42228
|
||||
MENUITEM "Opaque on &Hover", 42226
|
||||
}
|
||||
POPUP "&Scaling"
|
||||
{
|
||||
MENUITEM "&50%", 42214
|
||||
MENUITEM "&75%", 42215
|
||||
MENUITEM "&100%", 42216
|
||||
MENUITEM "150%", 42222
|
||||
MENUITEM "&200%", 42217
|
||||
MENUITEM "250%", 42218
|
||||
MENUITEM "&300%", 42219
|
||||
MENUITEM "&Custom", 42224
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "&Locked", 42223
|
||||
MENUITEM "&Temporary", 42225
|
||||
}
|
||||
POPUP "Docked Toolbar", 65535, MFT_STRING, MFS_GRAYED, 0
|
||||
{
|
||||
MENUITEM "Auto-&Hide", 42235
|
||||
MENUITEM "&Always On Top", 42234
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Top", 42229
|
||||
MENUITEM "Left", 42236
|
||||
MENUITEM "Right", 42231
|
||||
MENUITEM "Bottom", 42232
|
||||
MENUITEM "Not docked", 42233
|
||||
MENUITEM SEPARATOR
|
||||
MENUITEM "Dock/Undock Windows by Dragging", 42237
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const ffCustomScaleDialog = `1286 DIALOGEX 0, 0, 165, 70
|
||||
STYLE DS_SYSMODAL | DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
EXSTYLE WS_EX_TOOLWINDOW
|
||||
CAPTION "Custom Scale"
|
||||
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
||||
FONT 8, "MS Shell Dlg"
|
||||
{
|
||||
CONTROL "Scale : 100%", 1026, BUTTON, BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 7, 7, 151, 37
|
||||
CONTROL "Slider1", 1025, "msctls_trackbar32", TBS_HORZ | TBS_BOTH | TBS_NOTICKS | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 13, 17, 139, 11
|
||||
CONTROL "10%", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 17, 30, 14, 8
|
||||
CONTROL "300%", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 131, 30, 18, 8
|
||||
CONTROL "OK", 1, BUTTON, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 54, 50, 50, 13
|
||||
CONTROL "Cancel", 2, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 108, 50, 50, 13
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
124
packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts
Normal file
124
packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { Skin, UIRoot } from "../../UIRoot";
|
||||
import PopupMenu from "./PopupMenu";
|
||||
import { IMenuItem, IPopupMenu, MenuItem } from "./MenuItem";
|
||||
|
||||
type MenuActionEvent = (menu: MenuItem, uiRoot: UIRoot) => void;
|
||||
type MenuActionExecution = (uiRoot: UIRoot) => boolean | void;
|
||||
|
||||
type MenuAction = {
|
||||
onUpdate?: MenuActionEvent; //? attemp to update disability, checkmark, visiblity, etc
|
||||
onExecute?: MenuActionExecution; //? function to run when menu is clicked
|
||||
};
|
||||
const dummyAction: MenuAction = {
|
||||
onUpdate: (menu: MenuItem) => {},
|
||||
onExecute: (uiRoot: UIRoot) => false,
|
||||
};
|
||||
|
||||
export const actions: { [key: number]: MenuAction } = {};
|
||||
export const findAction = (menuId: number): MenuAction => {
|
||||
const registeredAction = actions[menuId] || {};
|
||||
return { ...dummyAction, ...registeredAction };
|
||||
};
|
||||
export async function updateActions(popup: IPopupMenu, uiRoot: UIRoot) {
|
||||
return await Promise.all(
|
||||
popup.children.map(async (menuItem) => {
|
||||
if (menuItem.type == "menuitem") {
|
||||
const action = findAction(menuItem.id);
|
||||
action.onUpdate(menuItem, uiRoot);
|
||||
} else if (menuItem.type == "popup") {
|
||||
await updateActions(menuItem.popup, uiRoot);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export const registerAction = (menuId: number, action: MenuAction) => {
|
||||
actions[menuId] = action;
|
||||
};
|
||||
|
||||
registerAction(40037, {
|
||||
//? Time elapsed
|
||||
onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {
|
||||
menu.checked = !uiRoot.audio._timeRemaining;
|
||||
},
|
||||
onExecute: (uiRoot: UIRoot) => {
|
||||
uiRoot.audio._timeRemaining = false;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
registerAction(40038, {
|
||||
//? Time remaining
|
||||
onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {
|
||||
menu.checked = uiRoot.audio._timeRemaining;
|
||||
},
|
||||
onExecute: (uiRoot: UIRoot) => {
|
||||
uiRoot.audio._timeRemaining = true;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
registerAction(40039, {
|
||||
//? Time remaining
|
||||
onExecute: (uiRoot: UIRoot) => {
|
||||
uiRoot.audio.toggleRemainingTime();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
registerAction(40044, {
|
||||
//? Previous
|
||||
onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("prev"),
|
||||
});
|
||||
|
||||
registerAction(40045, {
|
||||
//? Play
|
||||
onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("play"),
|
||||
});
|
||||
|
||||
registerAction(40046, {
|
||||
//? Pause
|
||||
onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("pause"),
|
||||
});
|
||||
|
||||
registerAction(40047, {
|
||||
//? Stop
|
||||
onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("stop"),
|
||||
});
|
||||
|
||||
registerAction(40048, {
|
||||
//? Next
|
||||
onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("next"),
|
||||
});
|
||||
|
||||
registerAction(11111140038, {
|
||||
//?
|
||||
onUpdate: (menu: MenuItem) => {},
|
||||
});
|
||||
|
||||
registerAction(40244, {
|
||||
//? Equalizer Enabled
|
||||
onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {
|
||||
menu.checked = uiRoot.audio._eqEnabled;
|
||||
},
|
||||
onExecute: (uiRoot: UIRoot) => {
|
||||
uiRoot.eq_toggle();
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
registerAction(40040, {
|
||||
//? View/ Playlist Editor
|
||||
onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {
|
||||
menu.checked = uiRoot.getActionState("toggle", "guid:pl");
|
||||
},
|
||||
onExecute: (uiRoot: UIRoot) => {
|
||||
uiRoot.dispatch("toggle", "guid:pl");
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
// registerAction(32767, { //? Skin, checked or not
|
||||
// onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getSkinName() == menu.caption},
|
||||
// onExecute: (uiRoot: UIRoot) => { uiRoot.switchSkin(menu.data as Skin); return true },
|
||||
// })
|
||||
|
|
@ -53,11 +53,10 @@ BEGIN
|
|||
BEGIN
|
||||
MENUITEM "&About ...", IDM_ABOUT,MFT_STRING,MFS_ENABLED
|
||||
END
|
||||
END`
|
||||
END`;
|
||||
|
||||
|
||||
// Untuk mengkonversi menu tersebut ke dalam format JSON,
|
||||
// Anda dapat melakukan parsing manual dengan melakukan split string dan looping.
|
||||
// Untuk mengkonversi menu tersebut ke dalam format JSON,
|
||||
// Anda dapat melakukan parsing manual dengan melakukan split string dan looping.
|
||||
// Berikut ini adalah contoh implementasi menggunakan JavaScript:
|
||||
|
||||
// Membuat fungsi untuk parsing string menu ke dalam format JSON
|
||||
|
|
@ -68,9 +67,9 @@ function parseMenuToJson(menuContent) {
|
|||
// let currentItem = null
|
||||
|
||||
// Looping setiap baris pada string menu
|
||||
for (let line of menuContent.split('\n')) {
|
||||
for (let line of menuContent.split("\n")) {
|
||||
// Mengabaikan baris yang tidak penting
|
||||
if (!line || line.trim().startsWith('//')) {
|
||||
if (!line || line.trim().startsWith("//")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -79,14 +78,21 @@ function parseMenuToJson(menuContent) {
|
|||
|
||||
// Mengecek apakah baris merupakan menu
|
||||
// const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i);
|
||||
const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i);
|
||||
const menuMatch = line.match(
|
||||
/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i
|
||||
);
|
||||
if (menuMatch) {
|
||||
// console.log('match', menuMatch)
|
||||
// Mengambil informasi menu
|
||||
let [, tag, t1, t2, id, flags] = menuMatch;
|
||||
const type = tag == 'POPUP' ? 'popup' : (t1 == 'SEPARATOR' || (flags || '').indexOf('MFT_SEPARATOR') >= 0) ? 'separator' : 'menuitem';
|
||||
const type =
|
||||
tag == "POPUP"
|
||||
? "popup"
|
||||
: t1 == "SEPARATOR" || (flags || "").indexOf("MFT_SEPARATOR") >= 0
|
||||
? "separator"
|
||||
: "menuitem";
|
||||
|
||||
flags = flags || ''
|
||||
flags = flags || "";
|
||||
// Membuat objek menu baru
|
||||
// @ts-ignore
|
||||
const menu: MenuItem = {
|
||||
|
|
@ -104,18 +110,18 @@ function parseMenuToJson(menuContent) {
|
|||
};
|
||||
container.push(menu); // attach to prent
|
||||
switch (menu.type) {
|
||||
case 'popup':
|
||||
case "popup":
|
||||
menu.caption = t2;
|
||||
menu.children = [];
|
||||
container = menu.children;
|
||||
if (flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
levelStack.push(container)
|
||||
if (flags.indexOf("GRAYED") >= 0) menu.disabled = true;
|
||||
levelStack.push(container);
|
||||
break;
|
||||
case 'menuitem':
|
||||
case "menuitem":
|
||||
menu.caption = t2;
|
||||
menu.id = id;
|
||||
// if(flags.indexOf('GRAYED') >= 0) menu.disabled = true;
|
||||
flags.indexOf('GRAYED') >= 0 && (menu.disabled = true)
|
||||
flags.indexOf("GRAYED") >= 0 && (menu.disabled = true);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -127,7 +133,7 @@ function parseMenuToJson(menuContent) {
|
|||
// @ts-ignore
|
||||
// menu.flags = flags;
|
||||
|
||||
console.log('m', menu)
|
||||
console.log("m", menu);
|
||||
|
||||
// Menambahkan objek menu baru ke dalam parent menu yang sesuai
|
||||
// if (level > levelStack[levelStack.length - 1]) {
|
||||
|
|
@ -142,7 +148,7 @@ function parseMenuToJson(menuContent) {
|
|||
// currentItem.children.push(newMenu);
|
||||
// currentItem = newMenu;
|
||||
// }
|
||||
} else if (['}', 'END'].includes(line.trim())) {
|
||||
} else if (["}", "END"].includes(line.trim())) {
|
||||
// Menutup menu saat ini
|
||||
levelStack.pop();
|
||||
container = levelStack[levelStack.length - 1];
|
||||
|
|
@ -152,8 +158,8 @@ function parseMenuToJson(menuContent) {
|
|||
return root;
|
||||
}
|
||||
|
||||
var m = parseMenuToJson(menuContent)
|
||||
console.log(m)
|
||||
var m = parseMenuToJson(menuContent);
|
||||
console.log(m);
|
||||
|
||||
var m = parseMenuToJson(another_sample)
|
||||
console.log(m)
|
||||
var m = parseMenuToJson(another_sample);
|
||||
console.log(m);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue