From cda5c0f052570852e8e292bc0a070f7e343af50b Mon Sep 17 00:00:00 2001 From: badlogic Date: Sat, 18 Sep 2021 01:58:14 +0200 Subject: [PATCH] [ts] Added CameraController, drag & drop example --- spine-ts/.vscode/launch.json | 7 + spine-ts/index.html | 1 + spine-ts/spine-core/src/AssetManagerBase.ts | 60 ++--- .../spine-webgl/example/drag-and-drop.html | 19 ++ spine-ts/spine-webgl/example/drag-and-drop.js | 220 ++++++++++++++++++ spine-ts/spine-webgl/src/CameraController.ts | 89 +++++++ spine-ts/spine-webgl/src/Input.ts | 108 ++++----- spine-ts/spine-webgl/src/index.ts | 1 + 8 files changed, 417 insertions(+), 88 deletions(-) create mode 100644 spine-ts/spine-webgl/example/drag-and-drop.html create mode 100644 spine-ts/spine-webgl/example/drag-and-drop.js create mode 100644 spine-ts/spine-webgl/src/CameraController.ts diff --git a/spine-ts/.vscode/launch.json b/spine-ts/.vscode/launch.json index e7bdc6cea..109a37100 100644 --- a/spine-ts/.vscode/launch.json +++ b/spine-ts/.vscode/launch.json @@ -10,6 +10,13 @@ "name": "Examples", "url": "http://localhost:8080", "webRoot": "${workspaceFolder}" + }, + { + "type": "pwa-chrome", + "request": "launch", + "name": "drag-and-drop", + "url": "http://localhost:8080/spine-webgl/example/drag-and-drop.html", + "webRoot": "${workspaceFolder}" } ] } \ No newline at end of file diff --git a/spine-ts/index.html b/spine-ts/index.html index f0dc7cde3..553914515 100644 --- a/spine-ts/index.html +++ b/spine-ts/index.html @@ -27,6 +27,7 @@
  • Example
  • Barebones
  • Mix & match
  • +
  • Drag & drop
  • Dress-up
  • Additive blending
  • Clipping
  • diff --git a/spine-ts/spine-core/src/AssetManagerBase.ts b/spine-ts/spine-core/src/AssetManagerBase.ts index c760ad83c..8783dbdbf 100644 --- a/spine-ts/spine-core/src/AssetManagerBase.ts +++ b/spine-ts/spine-core/src/AssetManagerBase.ts @@ -40,36 +40,36 @@ export class AssetManagerBase implements Disposable { private toLoad = 0; private loaded = 0; - constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = null) { + constructor(textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = null) { this.textureLoader = textureLoader; this.pathPrefix = pathPrefix; this.downloader = downloader || new Downloader(); } - private start (path: string): string { + private start(path: string): string { this.toLoad++; return this.pathPrefix + path; } - private success (callback: (path: string, data: any) => void, path: string, asset: any) { + private success(callback: (path: string, data: any) => void, path: string, asset: any) { this.toLoad--; this.loaded++; this.assets[path] = asset; if (callback) callback(path, asset); } - private error (callback: (path: string, message: string) => void, path: string, message: string) { + private error(callback: (path: string, message: string) => void, path: string, message: string) { this.toLoad--; this.loaded++; this.errors[path] = message; if (callback) callback(path, message); } - setRawDataURI (path: string, data: string) { + setRawDataURI(path: string, data: string) { this.downloader.rawDataUris[this.pathPrefix + path] = data; } - loadBinary (path: string, + loadBinary(path: string, success: (path: string, binary: Uint8Array) => void = null, error: (path: string, message: string) => void = null) { path = this.start(path); @@ -81,7 +81,7 @@ export class AssetManagerBase implements Disposable { }); } - loadText (path: string, + loadText(path: string, success: (path: string, text: string) => void = null, error: (path: string, message: string) => void = null) { path = this.start(path); @@ -93,7 +93,7 @@ export class AssetManagerBase implements Disposable { }); } - loadJson (path: string, + loadJson(path: string, success: (path: string, object: object) => void = null, error: (path: string, message: string) => void = null) { path = this.start(path); @@ -105,7 +105,7 @@ export class AssetManagerBase implements Disposable { }); } - loadTexture (path: string, + loadTexture(path: string, success: (path: string, texture: Texture) => void = null, error: (path: string, message: string) => void = null) { path = this.start(path); @@ -136,7 +136,7 @@ export class AssetManagerBase implements Disposable { } } - loadTextureAtlas (path: string, + loadTextureAtlas(path: string, success: (path: string, atlas: TextureAtlas) => void = null, error: (path: string, message: string) => void = null ) { @@ -170,11 +170,11 @@ export class AssetManagerBase implements Disposable { }); } - get (path: string) { + get(path: string) { return this.assets[this.pathPrefix + path]; } - require (path: string) { + require(path: string) { path = this.pathPrefix + path; let asset = this.assets[path]; if (asset) return asset; @@ -182,7 +182,7 @@ export class AssetManagerBase implements Disposable { throw Error("Asset not found: " + path + (error ? "\n" + error : "")); } - remove (path: string) { + remove(path: string) { path = this.pathPrefix + path; let asset = this.assets[path]; if ((asset).dispose) (asset).dispose(); @@ -190,7 +190,7 @@ export class AssetManagerBase implements Disposable { return asset; } - removeAll () { + removeAll() { for (let key in this.assets) { let asset = this.assets[key]; if ((asset).dispose) (asset).dispose(); @@ -198,27 +198,27 @@ export class AssetManagerBase implements Disposable { this.assets = {}; } - isLoadingComplete (): boolean { + isLoadingComplete(): boolean { return this.toLoad == 0; } - getToLoad (): number { + getToLoad(): number { return this.toLoad; } - getLoaded (): number { + getLoaded(): number { return this.loaded; } - dispose () { + dispose() { this.removeAll(); } - hasErrors () { + hasErrors() { return Object.keys(this.errors).length > 0; } - getErrors () { + getErrors() { return this.errors; } } @@ -227,7 +227,7 @@ export class Downloader { private callbacks: StringMap> = {}; rawDataUris: StringMap = {}; - dataUriToString (dataUri: string) { + dataUriToString(dataUri: string) { if (!dataUri.startsWith("data:")) { throw new Error("Not a data URI."); } @@ -241,17 +241,17 @@ export class Downloader { } } - base64ToArrayBuffer (base64: string) { + base64ToUint8Array(base64: string) { var binary_string = window.atob(base64); var len = binary_string.length; var bytes = new Uint8Array(len); for (var i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); } - return bytes.buffer; + return bytes; } - dataUriToUint8Array (dataUri: string) { + dataUriToUint8Array(dataUri: string) { if (!dataUri.startsWith("data:")) { throw new Error("Not a data URI."); } @@ -259,10 +259,10 @@ export class Downloader { let base64Idx = dataUri.indexOf("base64,"); if (base64Idx == -1) throw new Error("Not a binary data URI."); base64Idx += "base64,".length; - return this.base64ToArrayBuffer(dataUri.substr(base64Idx)); + return this.base64ToUint8Array(dataUri.substr(base64Idx)); } - downloadText (url: string, success: (data: string) => void, error: (status: number, responseText: string) => void) { + downloadText(url: string, success: (data: string) => void, error: (status: number, responseText: string) => void) { if (this.start(url, success, error)) return; if (this.rawDataUris[url]) { try { @@ -284,13 +284,13 @@ export class Downloader { request.send(); } - downloadJson (url: string, success: (data: object) => void, error: (status: number, responseText: string) => void) { + downloadJson(url: string, success: (data: object) => void, error: (status: number, responseText: string) => void) { this.downloadText(url, (data: string): void => { success(JSON.parse(data)); }, error); } - downloadBinary (url: string, success: (data: Uint8Array) => void, error: (status: number, responseText: string) => void) { + downloadBinary(url: string, success: (data: Uint8Array) => void, error: (status: number, responseText: string) => void) { if (this.start(url, success, error)) return; if (this.rawDataUris[url]) { try { @@ -317,7 +317,7 @@ export class Downloader { request.send(); } - private start (url: string, success: any, error: any) { + private start(url: string, success: any, error: any) { let callbacks = this.callbacks[url]; try { if (callbacks) return true; @@ -327,7 +327,7 @@ export class Downloader { } } - private finish (url: string, status: number, data: any) { + private finish(url: string, status: number, data: any) { let callbacks = this.callbacks[url]; delete this.callbacks[url]; let args = status == 200 ? [data] : [status, data]; diff --git a/spine-ts/spine-webgl/example/drag-and-drop.html b/spine-ts/spine-webgl/example/drag-and-drop.html new file mode 100644 index 000000000..13cc10f40 --- /dev/null +++ b/spine-ts/spine-webgl/example/drag-and-drop.html @@ -0,0 +1,19 @@ + + + + + + +
    + + +
    + + + + \ No newline at end of file diff --git a/spine-ts/spine-webgl/example/drag-and-drop.js b/spine-ts/spine-webgl/example/drag-and-drop.js new file mode 100644 index 000000000..8ddc8def3 --- /dev/null +++ b/spine-ts/spine-webgl/example/drag-and-drop.js @@ -0,0 +1,220 @@ +class App { + constructor() { + this.skeleton = null; + this.animationState = null; + this.canvas = null; + } + + loadAssets(canvas) { + this.canvas = canvas; + + // Load assets of Spineboy. + canvas.assetManager.loadBinary("assets/spineboy-pro.skel"); + canvas.assetManager.loadTextureAtlas("assets/spineboy-pma.atlas"); + } + + initialize(canvas) { + // Load the Spineboy skeleton + this.loadSkeleton("assets/spineboy-pro.skel", "assets/spineboy-pma.atlas", "run"); + + // Setup listener for animation selection box + let animationSelectBox = document.body.querySelector("#animations"); + animationSelectBox.onchange = () => { + this.animationState.setAnimation(0, animationSelectBox.value, true); + } + + // Setup the drag and drop listener + new FileDragAndDrop(canvas.htmlCanvas, (files) => this.onDrop(files)) + + // Setup a camera controller for paning and zooming + new spine.CameraController(canvas.htmlCanvas, canvas.renderer.camera); + } + + onDrop(files) { + let atlasFile; + let skeletonFile; + let pngs = []; + let assetManager = this.canvas.assetManager; + + // We use data URIs to load the dropped files. Some file types + // are binary, so we have to encode them to base64 for loading + // through AssetManager. + let bufferToBase64 = (buffer) => { + var binary = ''; + var bytes = new Uint8Array(buffer); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + return window.btoa(binary); + } + + for (var file of files) { + if (file.name.endsWith(".atlas") || file.name.endsWith(".atlas.txt")) { + atlasFile = file; + assetManager.setRawDataURI(file.name, "data:text/plain;," + file.contentText); + } else if (file.name.endsWith(".skel")) { + skeletonFile = file; + assetManager.setRawDataURI(file.name, "data:application/octet-stream;base64," + bufferToBase64(file.contentBinary)); + assetManager.loadBinary(file.name); + } else if (file.name.endsWith(".json")) { + skeletonFile = file; + assetManager.setRawDataURI(file.name, "data:text/plain;," + file.contentText); + assetManager.loadJson(file.name); + } else if (file.name.endsWith(".png")) { + pngs.push(file); + assetManager.setRawDataURI(file.name, "data:image/png;base64," + bufferToBase64(file.contentBinary)); + } + } + + if (!atlasFile) { + alert("Please provide a .atlas or .atlas.txt atlas file."); + return; + } + if (pngs.length == 0) { + alert("Please provide the atlas page .png file(s)."); + } + if (!skeletonFile) { + alert("Please provide a .skel or .json skeleton file."); + return; + } + + assetManager.loadTextureAtlas(atlasFile.name); + + let waitForLoad = () => { + if (this.canvas.assetManager.isLoadingComplete()) { + this.loadSkeleton(skeletonFile.name, atlasFile.name); + } else { + requestAnimationFrame(waitForLoad); + } + } + waitForLoad(); + } + + loadSkeleton(skeletonFile, atlasFile, animationName) { + // Load the skeleton and setup the animation state + let assetManager = this.canvas.assetManager; + var atlas = assetManager.require(atlasFile); + var atlasLoader = new spine.AtlasAttachmentLoader(atlas); + var skeletonData; + var skeletonBinaryOrJson = skeletonFile.endsWith(".skel") ? + new spine.SkeletonBinary(atlasLoader) : + new spine.SkeletonJson(atlasLoader); + skeletonBinaryOrJson.scale = 1; + skeletonData = skeletonBinaryOrJson.readSkeletonData(assetManager.require(skeletonFile)); + this.skeleton = new spine.Skeleton(skeletonData); + var animationStateData = new spine.AnimationStateData(skeletonData); + this.animationState = new spine.AnimationState(animationStateData); + + // Fill the animation selection box. + let animationSelectBox = document.body.querySelector("#animations"); + animationSelectBox.innerHTML = ""; + for (var animation of this.skeleton.data.animations) { + if (!animationName) animationName = animation.name; + let option = document.createElement("option"); + option.value = option.innerText = animation.name; + option.selected = animation.name == animationName; + animationSelectBox.appendChild(option); + } + + if (animationName) this.animationState.setAnimation(0, animationName, true); + + // Center the skeleton in the viewport + this.centerSkeleton(); + } + + centerSkeleton() { + // Calculate the bounds of the skeleton + this.animationState.update(0); + this.animationState.apply(this.skeleton); + this.skeleton.updateWorldTransform(); + let offset = new spine.Vector2(), size = new spine.Vector2(); + this.skeleton.getBounds(offset, size); + + // Make sure the canvas is sized properly and position and zoom + // the camera so the skeleton is centered in the viewport. + let renderer = this.canvas.renderer; + renderer.resize(spine.ResizeMode.Expand); + let camera = this.canvas.renderer.camera; + camera.position.x = offset.x + size.x / 2; + camera.position.y = offset.y + size.y / 2; + camera.zoom = size.x > size.y ? size.x / this.canvas.htmlCanvas.width * 3 : size.y / this.canvas.htmlCanvas.height * 3; + camera.update(); + } + + update(canvas, delta) { + this.animationState.update(delta); + this.animationState.apply(this.skeleton); + this.skeleton.updateWorldTransform(); + } + + render(canvas) { + let renderer = canvas.renderer; + renderer.resize(spine.ResizeMode.Expand); + + canvas.clear(0.2, 0.2, 0.2, 1); + renderer.begin(); + renderer.line(-10000, 0, 10000, 0, spine.Color.RED); + renderer.line(0, -10000, 0, 10000, spine.Color.GREEN); + renderer.drawSkeleton(this.skeleton, true); + renderer.end(); + } +} + +new spine.SpineCanvas(document.getElementById("canvas"), { + app: new App() +}); + +class FileDragAndDrop { + constructor(element, callback) { + this.callback = callback; + element.ondrop = (ev) => this.onDrop(ev); + element.ondragover = (ev) => ev.preventDefault(); + } + + async onDrop(event) { + event.preventDefault(); + event.stopPropagation(); + + const items = Object.keys(event.dataTransfer.items); + let files = []; + await Promise.all(items.map(async (key) => { + var file = event.dataTransfer.items[key].getAsFile(); + if (file.kind == "string") return; + let contentBinary = await file.arrayBuffer(); + let contentText = await file.text(); + files.push({ name: file.name, contentBinary: contentBinary, contentText: contentText }); + })); + this.callback(files); + } +} + +// Shim for older browsers for File/Blob.arrayBuffer() and .text() +(function () { + function arrayBuffer() { + return new Promise(function () { + let fr = new FileReader(); + fr.onload = () => { + resolve(fr.result); + }; + fr.readAsArrayBuffer(); + }) + } + + function text() { + return new Promise(function () { + let fr = new FileReader(); + fr.onload = () => { + resolve(fr.result); + }; + fr.readAsText(this); + }) + } + + if ('File' in self) { + File.prototype.arrayBuffer = File.prototype.arrayBuffer || arrayBuffer; + File.prototype.text = File.prototype.text || text; + } + Blob.prototype.arrayBuffer = Blob.prototype.arrayBuffer || arrayBuffer; + Blob.prototype.text = Blob.prototype.text || text; +})(); \ No newline at end of file diff --git a/spine-ts/spine-webgl/src/CameraController.ts b/spine-ts/spine-webgl/src/CameraController.ts new file mode 100644 index 000000000..31e03c9c9 --- /dev/null +++ b/spine-ts/spine-webgl/src/CameraController.ts @@ -0,0 +1,89 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated January 1, 2020. Replaces all prior versions. + * + * Copyright (c) 2013-2020, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software + * or otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +import { Input, Vector3 } from "src"; +import { OrthoCamera } from "./Camera"; + +export class CameraController { + constructor(public canvas: HTMLElement, public camera: OrthoCamera) { + let cameraX = 0, cameraY = 0, cameraZoom = 0; + let mouseX = 0, mouseY = 0; + let lastX = 0, lastY = 0; + + new Input(canvas).addListener({ + down: (x: number, y: number) => { + cameraX = camera.position.x; + cameraY = camera.position.y; + mouseX = lastX = x; + mouseY = lastY = y; + }, + dragged: (x: number, y: number) => { + let deltaX = x - mouseX; + let deltaY = y - mouseY; + let originWorld = camera.screenToWorld(new Vector3(0, 0), canvas.clientWidth, canvas.clientHeight); + let deltaWorld = camera.screenToWorld(new Vector3(deltaX, deltaY), canvas.clientWidth, canvas.clientHeight).sub(originWorld); + camera.position.set(cameraX - deltaWorld.x, cameraY - deltaWorld.y, 0); + camera.update(); + lastX = x; + lastY = y; + }, + zoom: (zoom: number) => { + let zoomAmount = zoom / 200 * camera.zoom; + let newZoom = camera.zoom + zoomAmount; + if (newZoom > 0) { + let x = 0, y = 0; + if (zoom < 0) { + x = lastX; y = lastY; + } else { + let viewCenter = new Vector3(canvas.clientWidth / 2 + 15, canvas.clientHeight / 2); + let mouseToCenterX = lastX - viewCenter.x; + let mouseToCenterY = canvas.clientHeight - 1 - lastY - viewCenter.y; + x = viewCenter.x - mouseToCenterX; + y = canvas.clientHeight - 1 - viewCenter.y + mouseToCenterY; + } + let oldDistance = camera.screenToWorld(new Vector3(x, y), canvas.clientWidth, canvas.clientHeight); + camera.zoom = newZoom; + camera.update(); + let newDistance = camera.screenToWorld(new Vector3(x, y), canvas.clientWidth, canvas.clientHeight); + camera.position.add(oldDistance.sub(newDistance)); + camera.update(); + } + console.log(`${camera.zoom}, ${zoomAmount}, ${zoom}`); + }, + up: (x: number, y: number) => { + lastX = x; + lastY = y; + }, + moved: (x: number, y: number) => { + lastX = x; + lastY = y; + }, + }); + } +} \ No newline at end of file diff --git a/spine-ts/spine-webgl/src/Input.ts b/spine-ts/spine-webgl/src/Input.ts index 0fa3a7098..836d98daa 100644 --- a/spine-ts/spine-webgl/src/Input.ts +++ b/spine-ts/spine-webgl/src/Input.ts @@ -41,12 +41,12 @@ export class Input { return new Touch(0, 0, 0); }); - constructor (element: HTMLElement) { + constructor(element: HTMLElement) { this.element = element; this.setupCallbacks(element); } - private setupCallbacks (element: HTMLElement) { + private setupCallbacks(element: HTMLElement) { let mouseDown = (ev: UIEvent) => { if (ev instanceof MouseEvent) { let rect = element.getBoundingClientRect(); @@ -104,9 +104,21 @@ export class Input { } } + let mouseWheel = (e: WheelEvent) => { + e.preventDefault(); + let deltaY = e.deltaY; + if (e.deltaMode == WheelEvent.DOM_DELTA_LINE) deltaY *= 8; + if (e.deltaMode == WheelEvent.DOM_DELTA_PAGE) deltaY *= 24; + let listeners = this.listeners; + for (let i = 0; i < listeners.length; i++) + if (listeners[i].zoom) listeners[i].zoom(e.deltaY); + }; + element.addEventListener("mousedown", mouseDown, true); element.addEventListener("mousemove", mouseMove, true); element.addEventListener("mouseup", mouseUp, true); + element.addEventListener("wheel", mouseWheel, true); + element.addEventListener("touchstart", (ev: TouchEvent) => { if (!this.currTouch) { var touches = ev.changedTouches; @@ -133,56 +145,7 @@ export class Input { } ev.preventDefault(); }, false); - element.addEventListener("touchend", (ev: TouchEvent) => { - if (this.currTouch) { - var touches = ev.changedTouches; - for (var i = 0; i < touches.length; i++) { - var touch = touches[i]; - if (this.currTouch.identifier === touch.identifier) { - let rect = element.getBoundingClientRect(); - let x = this.currTouch.x = touch.clientX - rect.left; - let y = this.currTouch.y = touch.clientY - rect.top; - this.touchesPool.free(this.currTouch); - let listeners = this.listeners; - for (let i = 0; i < listeners.length; i++) { - if (listeners[i].up) listeners[i].up(x, y); - } - this.lastX = x; - this.lastY = y; - this.buttonDown = false; - this.currTouch = null; - break; - } - } - } - ev.preventDefault(); - }, false); - element.addEventListener("touchcancel", (ev: TouchEvent) => { - if (this.currTouch) { - var touches = ev.changedTouches; - for (var i = 0; i < touches.length; i++) { - var touch = touches[i]; - if (this.currTouch.identifier === touch.identifier) { - let rect = element.getBoundingClientRect(); - let x = this.currTouch.x = touch.clientX - rect.left; - let y = this.currTouch.y = touch.clientY - rect.top; - this.touchesPool.free(this.currTouch); - let listeners = this.listeners; - for (let i = 0; i < listeners.length; i++) { - if (listeners[i].up) listeners[i].up(x, y); - } - - this.lastX = x; - this.lastY = y; - this.buttonDown = false; - this.currTouch = null; - break; - } - } - } - ev.preventDefault(); - }, false); element.addEventListener("touchmove", (ev: TouchEvent) => { if (this.currTouch) { var touches = ev.changedTouches; @@ -206,13 +169,41 @@ export class Input { } ev.preventDefault(); }, false); + + let touchEnd = (ev: TouchEvent) => { + if (this.currTouch) { + var touches = ev.changedTouches; + for (var i = 0; i < touches.length; i++) { + var touch = touches[i]; + if (this.currTouch.identifier === touch.identifier) { + let rect = element.getBoundingClientRect(); + let x = this.currTouch.x = touch.clientX - rect.left; + let y = this.currTouch.y = touch.clientY - rect.top; + this.touchesPool.free(this.currTouch); + let listeners = this.listeners; + for (let i = 0; i < listeners.length; i++) { + if (listeners[i].up) listeners[i].up(x, y); + } + + this.lastX = x; + this.lastY = y; + this.buttonDown = false; + this.currTouch = null; + break; + } + } + } + ev.preventDefault(); + }; + element.addEventListener("touchend", touchEnd, false); + element.addEventListener("touchcancel", touchEnd); } - addListener (listener: InputListener) { + addListener(listener: InputListener) { this.listeners.push(listener); } - removeListener (listener: InputListener) { + removeListener(listener: InputListener) { let idx = this.listeners.indexOf(listener); if (idx > -1) { this.listeners.splice(idx, 1); @@ -221,13 +212,14 @@ export class Input { } export class Touch { - constructor (public identifier: number, public x: number, public y: number) { + constructor(public identifier: number, public x: number, public y: number) { } } export interface InputListener { - down (x: number, y: number): void; - up (x: number, y: number): void; - moved (x: number, y: number): void; - dragged (x: number, y: number): void; + down?(x: number, y: number): void; + up?(x: number, y: number): void; + moved?(x: number, y: number): void; + dragged?(x: number, y: number): void; + zoom?(zoom: number): void; } diff --git a/spine-ts/spine-webgl/src/index.ts b/spine-ts/spine-webgl/src/index.ts index 64b3a3c02..6ba803726 100644 --- a/spine-ts/spine-webgl/src/index.ts +++ b/spine-ts/spine-webgl/src/index.ts @@ -1,5 +1,6 @@ export * from "./AssetManager"; export * from "./Camera"; +export * from "./CameraController"; export * from "./GLTexture"; export * from "./Input"; export * from "./LoadingScreen";