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 { 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; } } } } }