400 lines
11 KiB
400 lines
11 KiB
import { vec2 } from "../math/TSM";
|
|
import { CanvasKeyBoardEvent, CanvasMouseEvent, EInputEventType } from "./Event";
|
|
import { Timer, TimerCallback } from "./Timer";
|
|
|
|
|
|
export class Application implements EventListenerObject {
|
|
|
|
/**
|
|
* @var 定时器数组
|
|
*/
|
|
public timers: Timer[] = [];
|
|
|
|
/**
|
|
* @var 定时器id
|
|
*/
|
|
private _timerId: number = -1;
|
|
|
|
/**
|
|
* @var 帧率
|
|
*/
|
|
private _fps: number = 0;
|
|
|
|
/**
|
|
* @var
|
|
*/
|
|
public isFlipYCoord: boolean = false;
|
|
|
|
/**
|
|
* @var
|
|
*/
|
|
public canvas: HTMLCanvasElement;
|
|
|
|
/**
|
|
* @var 是否每次移动都会触发mouse事件
|
|
*/
|
|
public isSupportMouseMove: boolean;
|
|
|
|
/**
|
|
* @var 鼠标是否被按下
|
|
*/
|
|
protected _isMouseDown: boolean;
|
|
|
|
/**
|
|
* @var 是否按下鼠标右键
|
|
*/
|
|
protected _isRightMouseDown: boolean = false;
|
|
|
|
/**
|
|
* @var 是否进入循环
|
|
*/
|
|
protected _start: boolean = false;
|
|
|
|
/**
|
|
* @var 由Window对象的requestAnimationFrame返回的大于0的id号,
|
|
* 我们可以使用cancelAnimationFrame ( this ._requestId )来取消动画循环.
|
|
*/
|
|
protected _requestId: number = -1;
|
|
|
|
/**
|
|
* @var 计算当前更新与上一次更新之间的时间差
|
|
*/
|
|
protected _lastTime !: number;
|
|
protected _startTime !: number;
|
|
|
|
|
|
/**
|
|
* @function 渲染每帧画面的回调函数
|
|
*/
|
|
public frameCallback: ((app: Application) => void) | null;
|
|
|
|
|
|
|
|
public constructor(canvas: HTMLCanvasElement) {
|
|
this.canvas = canvas;
|
|
|
|
//设置鼠标的监听事件
|
|
this.canvas.addEventListener("mousedown", this, false);
|
|
this.canvas.addEventListener("mouseup", this, false);
|
|
this.canvas.addEventListener("mousemove", this, false);
|
|
|
|
//设置键盘监听事件
|
|
window.addEventListener("keydown", this, false);
|
|
window.addEventListener("keyup", this, false);
|
|
window.addEventListener("keypress", this, false);
|
|
|
|
//初始化时候mousedown 和 mousemvoe不开启
|
|
this._isMouseDown = false;
|
|
this.isSupportMouseMove = false;
|
|
|
|
this.frameCallback = null;
|
|
|
|
//禁止右键菜单
|
|
document.oncontextmenu = function () { return false };
|
|
}
|
|
|
|
|
|
/**
|
|
* 判断当前Application是否一直在调用requestAnimationFrame
|
|
* @returns boolean
|
|
*/
|
|
public isRunning(): boolean {
|
|
return this._start;
|
|
}
|
|
|
|
/**
|
|
* 获取当前帧率
|
|
*/
|
|
public get fps() {
|
|
return this._fps;
|
|
}
|
|
|
|
/**
|
|
* 启动渲染
|
|
*/
|
|
public start(): void {
|
|
if (this._start === false) {
|
|
this._start = true;
|
|
|
|
this._startTime = -1;
|
|
this._lastTime = -1;
|
|
|
|
this._requestId = requestAnimationFrame((msec: number): void => {
|
|
this.step(msec);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 循环函数
|
|
* @param timeStamp
|
|
*/
|
|
protected step(timeStamp: number): void {
|
|
|
|
if (this._startTime === -1) {
|
|
this._startTime = timeStamp;
|
|
}
|
|
|
|
if (this._lastTime === -1) {
|
|
this._lastTime = timeStamp;
|
|
}
|
|
|
|
//计算当前时间和第一次调用step时候的时间差
|
|
let elapsedMsec = timeStamp - this._startTime;
|
|
|
|
//计算当前时间和上一次调用step时候的时间差
|
|
let intervalSec = timeStamp - this._lastTime;
|
|
this._lastTime = timeStamp;
|
|
|
|
//计算帧率
|
|
if (intervalSec !== 0) {
|
|
this._fps = 1000 / intervalSec;
|
|
}
|
|
|
|
intervalSec /= 1000.0; //转成秒
|
|
|
|
//处理timer
|
|
this._handleTimers(intervalSec);
|
|
|
|
//更新
|
|
this.update(elapsedMsec, intervalSec);
|
|
|
|
//渲染
|
|
this.render();
|
|
|
|
if (this.frameCallback !== null) {
|
|
this.frameCallback(this);
|
|
}
|
|
|
|
requestAnimationFrame((elapsedMsec: number): void => {
|
|
this.step(elapsedMsec);
|
|
})
|
|
}
|
|
|
|
public stop(): void {
|
|
if (this._start) {
|
|
cancelAnimationFrame(this._requestId);
|
|
this._startTime = -1;
|
|
this._lastTime = -1;
|
|
this._start = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 虚函数
|
|
* @param elapsedMsec 毫秒
|
|
* @param intervalSec 秒
|
|
*/
|
|
public update(elapsedMsec: number, intervalSec: number): void { }
|
|
|
|
/**
|
|
* 虚函数
|
|
*/
|
|
public render(): void { }
|
|
|
|
public async run(): Promise<void> {
|
|
this.start();
|
|
}
|
|
|
|
handleEvent(evt: Event): void {
|
|
switch (evt.type) {
|
|
case "mousedown":
|
|
this._isMouseDown = true;
|
|
this.onMouseDown(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEDOWN));
|
|
break;
|
|
case "mouseup":
|
|
this._isMouseDown = false;
|
|
this.onMouseDown(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEUP));
|
|
break;
|
|
case "mousemove":
|
|
if (this.isSupportMouseMove) {
|
|
this.onMouseMove(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEMOVE));
|
|
}
|
|
if (this._isMouseDown) {
|
|
this.onMouseDrag(this._toCanvasMouseEvent(evt, EInputEventType.MOUSEDRAG));
|
|
}
|
|
break;
|
|
case "keypress":
|
|
this.onKeyPress(this._toCanvasKeyBoardEvent(evt, EInputEventType.KEYPRESS));
|
|
break;
|
|
case "keydown":
|
|
this.onKeyDown(this._toCanvasKeyBoardEvent(evt, EInputEventType.MOUSEDOWN));
|
|
break;
|
|
case "keyup":
|
|
this.onKeyUp(this._toCanvasKeyBoardEvent(evt, EInputEventType.KEYUP));
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected onMouseDown(evt: CanvasMouseEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected onMouseUp(evt: CanvasMouseEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected onMouseMove(evt: CanvasMouseEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected onMouseDrag(evt: CanvasMouseEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected onKeyDown(evt: CanvasKeyBoardEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected onKeyUp(evt: CanvasKeyBoardEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected onKeyPress(evt: CanvasKeyBoardEvent): void {
|
|
return;
|
|
}
|
|
|
|
protected getMouseCanvas(): HTMLCanvasElement {
|
|
return this.canvas;
|
|
}
|
|
|
|
/**
|
|
* 将鼠标的指针位置变换为当前canvas的位置
|
|
* @param evt
|
|
*/
|
|
private viewportToCanvasCoordinate(evt: MouseEvent): vec2 {
|
|
let rect: ClientRect = this.getMouseCanvas().getBoundingClientRect();
|
|
|
|
//target是触发事件的元素,这里是canvas
|
|
if (evt.target) {
|
|
let x: number = evt.clientX - rect.left;
|
|
let y: number = evt.clientY - rect.top;
|
|
|
|
if (this.isFlipYCoord) {
|
|
y = this.getMouseCanvas().height - y;
|
|
}
|
|
let pos: vec2 = new vec2([x, y]);
|
|
return pos;
|
|
}
|
|
|
|
throw new Error("evt.target is null");
|
|
}
|
|
|
|
/**
|
|
* 把DOM 事件转成自己的事件
|
|
* @param evt
|
|
* @param type
|
|
*/
|
|
private _toCanvasMouseEvent(evt: Event, type: EInputEventType): CanvasMouseEvent {
|
|
let event: MouseEvent = evt as MouseEvent;//向下转型,将Event转换为MouseEvent
|
|
|
|
if (type === EInputEventType.MOUSEDOWN && event.button === 2) {
|
|
this._isRightMouseDown = true;
|
|
} else if (type === EInputEventType.MOUSEUP && event.button === 2) {
|
|
this._isRightMouseDown = false;
|
|
}
|
|
|
|
let buttonNum: number = event.button;
|
|
|
|
if (this._isRightMouseDown && type === EInputEventType.MOUSEDRAG) {
|
|
buttonNum = 2;
|
|
}
|
|
|
|
//将鼠标位置变换到canvas坐标系位置
|
|
let mousePosition: vec2 = this.viewportToCanvasCoordinate(event);
|
|
|
|
let ret: CanvasMouseEvent = new CanvasMouseEvent(type, mousePosition, buttonNum, event.altKey, event.ctrlKey, event.shiftKey);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 把DOM 事件转成自己的事件
|
|
* @param evt
|
|
* @param type
|
|
*/
|
|
private _toCanvasKeyBoardEvent(evt: Event, type: EInputEventType): CanvasKeyBoardEvent {
|
|
let event: KeyboardEvent = evt as KeyboardEvent;
|
|
let ret: CanvasKeyBoardEvent = new CanvasKeyBoardEvent(type, event.key, event.keyCode, event.repeat, event.altKey, event.ctrlKey, event.shiftKey);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* 添加一个定时器
|
|
* @param callback
|
|
* @param timeout
|
|
* @param onlyOnce
|
|
* @param data
|
|
* @returns
|
|
*/
|
|
public addTimer(callback: TimerCallback, timeout: number = 1.0, onlyOnce: boolean = false, data: any = undefined): number {
|
|
let timer: Timer;
|
|
|
|
for (let i = 0; i < this.timers.length; i++) {
|
|
timer = this.timers[i];
|
|
if (timer.enabled === false) {
|
|
timer.callback = callback;
|
|
timer.callbackData = data;
|
|
timer.timeout = timeout;
|
|
timer.enabled = true;
|
|
timer.onlyOnce = onlyOnce;
|
|
return timer.id;
|
|
}
|
|
}
|
|
|
|
timer = new Timer(callback);
|
|
timer.callback = callback;
|
|
timer.callbackData = data;
|
|
timer.timeout = timeout;
|
|
timer.enabled = true;
|
|
timer.onlyOnce = onlyOnce;
|
|
|
|
this.timers.push(timer);
|
|
timer.id = ++this._timerId;
|
|
|
|
return timer.id;
|
|
}
|
|
|
|
/**
|
|
* 清除定时器
|
|
* @param id
|
|
* @returns
|
|
*/
|
|
public removeTimer(id: number): boolean {
|
|
let ret: boolean = false;
|
|
for (let i = 0; i < this.timers.length; i++) {
|
|
if (this.timers[i].id === id) {
|
|
let timer: Timer = this.timers[i];
|
|
timer.enabled = false;
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param intervalSec 秒
|
|
*/
|
|
private _handleTimers(intervalSec: number): void {
|
|
for (let i = 0; i < this.timers.length; i++) {
|
|
let timer: Timer = this.timers[i];
|
|
|
|
if (timer.enabled === false) {
|
|
continue;
|
|
}
|
|
|
|
//countdown 初始化为timeout, 每次减少渲染时间
|
|
timer.countdown -= intervalSec;
|
|
|
|
if (timer.countdown <= 0.0) {
|
|
timer.callback(timer.id, timer.callbackData);
|
|
if (timer.onlyOnce) {
|
|
this.removeTimer(timer.id);
|
|
} else {
|
|
timer.callbackData = timer.timeout;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|