Support dragging in a skin

This commit is contained in:
Jordan Eldredge 2021-07-03 16:14:04 -07:00
parent ca16dbe3a2
commit d8c4a571bf
7 changed files with 133 additions and 64 deletions

View file

@ -4,10 +4,11 @@ import TrueTypeFont from "./skin/TrueTypeFont";
import { assert, assume } from "./utils";
import BitmapFont from "./skin/BitmapFont";
import Color from "./skin/Color";
import AUDIO_PLAYER from "./skin/AudioPlayer";
import GammaGroup from "./skin/GammaGroup";
import Container from "./skin/Container";
import Vm from "./skin/VM";
import BaseObject from "./skin/BaseObject";
import AudioPlayer from "./skin/AudioPlayer";
export class UIRoot {
// Just a temporary place to stash things
@ -20,10 +21,32 @@ export class UIRoot {
_activeGammaSet: GammaGroup[] | null = null;
_containers: Container[] = [];
vm: Vm;
// A list of all objects created for this skin.
_objects: BaseObject[] = [];
vm: Vm = new Vm();
audio: AudioPlayer = new AudioPlayer();
reset() {
this.dispose();
this._bitmaps = [];
this._fonts = [];
this._colors = [];
this._groupDefs = [];
this._gammaSets = new Map();
this._xuiElements = [];
this._activeGammaSet = null;
this._containers = [];
// A list of all objects created for this skin.
this._objects = [];
constructor() {
this.vm = new Vm();
this.audio = new AudioPlayer();
}
addObject(obj: BaseObject) {
this._objects.push(obj);
}
addBitmap(bitmap: Bitmap) {
@ -127,7 +150,12 @@ export class UIRoot {
const found = this._activeGammaSet.find((gammaGroup) => {
return gammaGroup.getId().toLowerCase() === lower;
});
assume(found != null, `Cold not find a gammagroup for "${id}"`);
assume(
found != null,
`Cold not find a gammagroup for "${id}" from ${Array.from(
this._gammaSets.keys()
).join(", ")}`
);
return found;
}
@ -168,29 +196,35 @@ export class UIRoot {
dispatch(action: string, param: string | null, actionTarget: string | null) {
switch (action) {
case "PLAY":
AUDIO_PLAYER.play();
this.audio.play();
break;
case "PAUSE":
AUDIO_PLAYER.pause();
this.audio.pause();
break;
case "STOP":
AUDIO_PLAYER.stop();
this.audio.stop();
break;
case "NEXT":
AUDIO_PLAYER.next();
this.audio.next();
break;
case "PREV":
AUDIO_PLAYER.previous();
this.audio.previous();
break;
case "EJECT":
AUDIO_PLAYER.eject();
this.audio.eject();
break;
default:
assume(false, `Unknown global action: ${action}`);
}
}
dispose() {
for (const obj of this._objects) {
obj.dispose();
}
}
}
// Global Singleton for now
const UI_ROOT = new UIRoot();
let UI_ROOT = new UIRoot();
export default UI_ROOT;

View file

@ -0,0 +1,36 @@
export function addDropHandler(onDrop: (blob: Blob) => void) {
const dropTarget = document.createElement("div");
dropTarget.style.top = "0px";
dropTarget.style.left = "0px";
dropTarget.style.position = "absolute";
dropTarget.style.bottom = "0";
dropTarget.style.right = "0";
document.body.appendChild(dropTarget);
function dropHandler(ev: DragEvent) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
const file = ev.dataTransfer.files[0];
if (file == null) {
return;
}
clearDropTargetStyle();
onDrop(file);
}
function clearDropTargetStyle() {
dropTarget.style.border = "none";
}
function dragOverHandler(ev: DragEvent) {
dropTarget.style.border = "4px dotted white";
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
}
dropTarget.addEventListener("dragover", dragOverHandler);
dropTarget.addEventListener("dragleave", clearDropTargetStyle);
dropTarget.addEventListener("drop", dropHandler);
}

View file

@ -2,6 +2,9 @@ import JSZip from "jszip";
// This module is imported early here in order to avoid a circular dependency.
import { classResolver } from "./skin/resolver";
import SkinParser from "./skin/parse";
import UI_ROOT from "./UIRoot";
import { removeAllChildNodes } from "./utils";
import { addDropHandler } from "./dropTarget";
function hack() {
// Without this Snowpack will try to treeshake out resolver causing a circular
@ -9,58 +12,51 @@ function hack() {
classResolver("A funny joke about why this is needed.");
}
const NODE = document.createElement("div");
document.body.appendChild(NODE);
const STATUS = document.getElementById("status");
function setStatus(status: string) {
STATUS.innerText = status;
}
async function main() {
const status = document.getElementById("status");
status.innerText = "Downloading skin...";
addDropHandler(loadSkin);
setStatus("Downloading skin...");
// const response = await fetch("assets/CornerAmp_Redux.wal");
// const response = await fetch("assets/Default_winamp3_build499.wal");
const response = await fetch("assets/MMD3.wal");
const data = await response.blob();
status.innerText = "Loading .wal archive...";
const zip = await JSZip.loadAsync(data);
await loadSkin(data);
}
status.innerText = "Parsing XML and initializing images...";
const parser = new SkinParser(zip);
async function loadSkin(skinData: Blob) {
removeAllChildNodes(NODE);
UI_ROOT.reset();
setStatus("Loading .wal archive...");
const zip = await JSZip.loadAsync(skinData);
setStatus("Parsing XML and initializing images...");
const parser = new SkinParser(zip, UI_ROOT);
const uiRoot = await parser.parse();
let node = document.createElement("div");
status.innerText = "Enabling Colors...";
setStatus("Enabling Colors...");
uiRoot.enableDefaultGammaSet();
status.innerText = "Rendering skin for the first time...";
setStatus("Rendering skin for the first time...");
for (const container of uiRoot.getContainers()) {
container.draw();
node.appendChild(container.getDiv());
NODE.appendChild(container.getDiv());
}
const select = document.createElement("select");
select.style.position = "absolute";
select.style.bottom = "0px";
select.style.left = "0px";
select.addEventListener("change", (e) => {
uiRoot.enableGammaSet((e.target as HTMLInputElement).value);
});
for (const set of uiRoot._gammaSets.keys()) {
const option = document.createElement("option");
option.innerText = set;
option.value = set;
select.appendChild(option);
}
document.body.appendChild(select);
const div = document.createElement("div");
document.body.appendChild(div);
document.body.appendChild(node);
status.innerText = "Initializing Maki...";
setStatus("Initializing Maki...");
for (const container of uiRoot.getContainers()) {
container.init();
}
status.innerText = "";
setStatus("");
}
main();

View file

@ -1,4 +1,4 @@
class AudioPlayer {
export default class AudioPlayer {
_input: HTMLInputElement = document.createElement("input");
_audio: HTMLAudioElement = document.createElement("audio");
constructor() {
@ -53,7 +53,3 @@ class AudioPlayer {
return this._audio.duration;
}
}
const AUDIO_PLAYER = new AudioPlayer();
export default AUDIO_PLAYER;

View file

@ -1,11 +1,12 @@
/**
Object Class.
import UI_ROOT from "../UIRoot";
@short This is the base class from which all other classes inherit.
@author Nullsoft Inc.
@ver 1.0
*/
/**
* This is the base class from which all other classes inherit.
*/
export default class BaseObject {
constructor() {
UI_ROOT.addObject(this);
}
/**
* Returns the class name for the object.
*
@ -18,4 +19,8 @@ export default class BaseObject {
getId() {
throw new Error("Unimplemented");
}
dispose() {
// Pass
}
}

View file

@ -1,6 +1,5 @@
import { getClass } from "../maki/objects";
import { ParsedMaki } from "../maki/parser";
import AUDIO_PLAYER from "./AudioPlayer";
import BaseObject from "./BaseObject";
import Container from "./Container";
import { clamp } from "../utils";
@ -148,7 +147,7 @@ export default class SystemObject extends BaseObject {
* @ret The current volume.
**/
getvolume() {
return AUDIO_PLAYER.getVolume() * 255;
return UI_ROOT.audio.getVolume() * 255;
}
/**
@ -160,7 +159,7 @@ export default class SystemObject extends BaseObject {
setvolume(_vol: number) {
const vol = clamp(_vol, 0, 255);
AUDIO_PLAYER.setVolume(vol / 255);
UI_ROOT.audio.setVolume(vol / 255);
}
/**
@ -169,7 +168,7 @@ export default class SystemObject extends BaseObject {
* @ret Length of the track, in seconds.
*/
getplayitemlength(): number {
return AUDIO_PLAYER.getLength();
return UI_ROOT.audio.getLength();
//
}
@ -179,7 +178,7 @@ export default class SystemObject extends BaseObject {
*/
seekto(pos: number) {
// Note: For some reason I seem to be getting passed seconds here not MS
AUDIO_PLAYER.seekTo(pos);
UI_ROOT.audio.seekTo(pos);
}
/**

View file

@ -22,7 +22,7 @@ import BitmapFont from "./BitmapFont";
import Color from "./Color";
import GammaGroup from "./GammaGroup";
import ColorThemesList from "./ColorThemesList";
import UI_ROOT, { UIRoot } from "../UIRoot";
import { UIRoot } from "../UIRoot";
class ParserContext {
container: Container | null = null;
@ -37,10 +37,13 @@ export default class SkinParser {
_gammaSet: GammaGroup[] = [];
_uiRoot: UIRoot;
constructor(zip: JSZip) {
constructor(
zip: JSZip,
uiRoot: UIRoot /* Once UI_ROOT is not a singleton, we can create that objet in the constructor */
) {
this._zip = zip;
this._imageManager = new ImageManager(zip);
this._uiRoot = UI_ROOT;
this._uiRoot = uiRoot;
}
async parse(): Promise<UIRoot> {
// Load built-in xui elements