mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-25 19:13:54 +00:00
Support dragging in a skin
This commit is contained in:
parent
ca16dbe3a2
commit
d8c4a571bf
7 changed files with 133 additions and 64 deletions
|
|
@ -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;
|
||||
|
|
|
|||
36
packages/webamp-modern-2/src/dropTarget.ts
Normal file
36
packages/webamp-modern-2/src/dropTarget.ts
Normal 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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue