jsdk-offical
Version:
JSDK is the most comprehensive TypeScript framework, like JDK.
296 lines (265 loc) • 10.8 kB
text/typescript
/**
* @project JSDK
* @license MIT
* @website https://github.com/fengboyue/jsdk
*
* @version 2.2.0
* @author Frank.Feng
*/
/// <reference path="../ds/Queue.ts"/>
/// <reference path="VK.ts"/>
/// <reference path="Keyboards.ts"/>
module JS {
export namespace input {
let J = Jsons;
/**
* keychar1 , keychar2 , ... , keycharN-1, keycharN|Hotkeys <br>
* <br>
* Examples:
* <pre>
* A,B,C,D
* A,B,C+D
* </pre>
*/
export type Seqkeys = string;
/**
* keychar1 + keychar2 + ... + keycharN <br>
* <br>
* Examples:
* <pre>
* A+B+C+D
* </pre>
*/
export type Hotkeys = string;
export class Keys {
private _m: JsonObject<number>;//用于记录所有当前正在按住的键的按下时间戳;
private _q: Queue<number>;//Queue<keyCode> 用于记录16个最近按下的键
private _mapping: JsonObject<string> = {}; //<keyChars, keyCodes>
private _busDown: EventBus;
private _busUp: EventBus;
private _d: boolean = false;
private _i: number = Infinity; //unit is ms, max interval time of Seqkeys
private _ts: number = 0; //上一次的按键时刻for seqKeys
constructor(el?: HTMLElement) {
let ele = <any>el || window, T = this;
T._m = {};
T._q = new Queue(16);
T._busDown = new EventBus(ele);
T._busUp = new EventBus(ele);
ele.on('keydown', (e: KeyboardEvent) => {
let c = e.keyCode,
sz = T._q.size(),
lastC = T._q.get(sz - 1),
repeat = sz > 0 && c === lastC;
if (T._q.isFull()) T._q.remove();
//不记录重复按键
if (!repeat) {
//第一次则认为上次按键即现在
if(lastC == null) T._ts = e.timeStamp;
//小于最大间隔时间才被记录
if (e.timeStamp - T._ts <= T._i)
T._q.add(c)
}
//记录按键时间
T._ts = e.timeStamp;
//没记录或与上一次按键不同的键,则更新的时间戳
if (!J.hasKey(T._m, c) || !repeat) T._m[c] = e.timeStamp;
//不重复且有记录时,作事件触发检查
if (!repeat && J.hasKey(T._m, c)) T._fireCheck(c, T._busDown)
});
ele.on('keyup', (e: KeyboardEvent) => {
let c = e.keyCode;
if (J.hasKey(T._m, c)) {
T._fireCheck(c, T._busUp);
//按键释放后删除记录
delete T._m[e.keyCode];
}
})
}
private _fireCheck(c: number, bus: EventBus){
let T = this, types = bus.types();
types.forEach(ty => {
if (T.isHotKeys(ty) && T._endsWithCode(c, ty, '+') && T._isHotKeysPressing(ty))
bus.fire(Keyboards.newEvent(ty, {keyCode: c}), [this]);
if (T.isSeqKeys(ty) && T._endsWithCode(c, ty, ',') && T._isSeqKeysPressing(ty))
bus.fire(Keyboards.newEvent(ty, {keyCode: c}), [this]);
if (VK[ty] == c && T.isPressingKey(c))
bus.fire(Keyboards.newEvent(ty, {keyCode: c}), [this]);
})
}
private _endsWithCode(c: number, ty: string, sn: string) {
return (this._mapping[ty] + sn).endsWith(c + sn)
}
public isSeqKeys(k: string) {
return k && k.indexOf(',') > 0
}
public isHotKeys(k: string) {
return k && k.indexOf('+') > 0
}
private _on(k: string, fn: Function, bus: EventBus) {
let T = this, ty = T._keyChar(k);
//keyChar表达式转换为keyCode表达式并缓存
if (!J.hasKey(T._mapping, ty)) T._mapping[ty] = T._numeric(ty, T.isHotKeys(ty) ? '+' : (T.isSeqKeys(ty) ? ',' : ''));
bus.on(ty, fn);
return T
}
public onKeyDown(k: Hotkeys | Seqkeys, fn: (this: Window | HTMLElement, e: KeyboardEvent, kb: Keys) => void) {
return this._on(k, fn, this._busDown)
}
public onKeyUp(k: Hotkeys | Seqkeys, fn: (this: Window | HTMLElement, e: KeyboardEvent, kb: Keys) => void) {
return this._on(k, fn, this._busUp)
}
private _off(bus: EventBus, k?: Hotkeys | Seqkeys) {
this._check();
bus.off(k ? this._keyChar(k) : undefined)
return this
}
public offKeyDown(k?: Hotkeys | Seqkeys) {
return this._off(this._busDown, k)
}
public offKeyUp(k?: Hotkeys | Seqkeys) {
return this._off(this._busUp, k)
}
private _equalsSeqkeys(keys: Array<string>, keyCodes: string) {
let sa = '';
keys.forEach((b, i) => {
if (i == 0) {
sa += VK[b]
} else {
sa += `,${VK[b]}`
}
})
return keyCodes.endsWith(sa + ']')
}
private _isSeqKeysPressing(k: Seqkeys) {
let a = k.split('\,'), l = a.length;
if (l == 1) return false;
let lk = a[l - 1], m = this, codes = this._q.toString();//取最后一位keyChar
if (m.isHotKeys(lk)) {//可能是hotkeys
if (!m._isHotKeysPressing(lk)) return false;
a.remove(l - 1);
a.add(lk.split('\+'))
} else {
if (!m.isPressingKey(lk)) return false;
}
return this._equalsSeqkeys(a, codes)
}
private _keyChar(s: string) {
return s.replace(/\s*/g, '').toUpperCase()
}
private _isHotKeysPressing(k: Hotkeys) {
let T = this, s = T._keyChar(k), a = s.split('\+');
if (a.length == 1) return false;
return a.every((b, i) => {
if (i > 0 && !T.beforeKeyDown(a[i - 1], b)) return false
return T.isPressingKey(b)
})
}
private _numeric(ty: string, sign: string) {
if (!sign) return VK[ty];
let a = ty.split(sign), sk = '';
a.forEach(k => {
sk += `${!sk ? '' : sign}${VK[k.toUpperCase()]}`
})
return sk
}
/**
* Whether a hotkeys or a seqkeys or a keyChar is pressing.
* @param keys
*/
public isPressingKeys(keys: Hotkeys | Seqkeys | string) {
let T = this, k = T._keyChar(keys);
if (!k) return false;
if (T.isSeqKeys(k)) {
return T._isSeqKeysPressing(k)
} else if (T.isHotKeys(k)) {
return T._isHotKeysPressing(k)
}
return this.isPressingKey(VK[k])
}
/**
* True if the key code is pressing.
*
* @param c keyCode or keyChar
*/
public isPressingKey(c: number | string) {
let T = this, n = c == void 0 ? null : (Types.isNumber(c) ? c : VK[T._keyChar(<string>c)]);
return J.hasKey(T._m, n)
}
/**
* Returns the clone queue<keyCode> of current pressing keys.<br>
* Note: Max size of queue is 16.
*/
public getPressingQueue() {
return this._q.clone()
}
/**
* Gets/Sets the max interval time of Seqkeys.
*/
public seqInterval(): number
public seqInterval(t: number): this
public seqInterval(t?: number): any {
if (t == void 0) return this._i;
this._i = t;
return this
}
/**
* Returns the timeStamp(ms) of a key when it was keydown recently.<br>
* Note: If no record of the key then returns 0.
*
* @param c keyCode or keyChar
*/
public getKeyDownTime(c: number | string): number {
let T = this, n = c == void 0 ? null : (Types.isNumber(c) ? c : VK[T._keyChar(<string>c)]);
return !J.hasKey(T._m, n) ? 0 : T._m[n]
}
/**
* Returns true if keyCode1's keydown time is before than keyCode2's recently.<br>
* Note: If no records of keyCode1 or keyCode2 then returns false.
* @param k1 keyCode1
* @param k2 KeyCode2
*/
public beforeKeyDown(k1: number | string, k2: number | string): boolean {
let d1 = this.getKeyDownTime(k1), d2 = this.getKeyDownTime(k2);
return d1 > 0 && d2 > 0 && d1 < d2
}
public off() {
let T = this;
T._check();
T._busDown.off();
T._busUp.off();
return T
}
/**
* Clear all records or one keyCode's record.
* @param c keyCode
*/
public clear(c?: number | Array<number>) {
let T = this;
if (c == void 0) {
T._mapping = {};
T._m = {};
T._q.clear();
T._ts = 0;
return
}
let a = Types.isNumber(c) ? [c] : <Number[]>c;
a.forEach(k => {
T._m[k] = null;
})
return T
}
private _check() {
if (this._d) throw new RefusedError()
}
public destroy() {
let T = this;
T._d = true;
T.clear();
T._busDown.destroy();
T._busUp.destroy()
}
}
}
}
import Keys = JS.input.Keys;