feat(gesture): Introducing new gesture controller

This commit is contained in:
Manu Mtz.-Almeida
2016-07-06 03:04:47 +02:00
parent d6f62bcb60
commit 9f19023cb9
25 changed files with 1035 additions and 124 deletions

View File

@ -1,5 +1,5 @@
import {Gesture} from './gesture';
import {defaults} from '../util';
import { Gesture } from './gesture';
import { defaults } from '../util';
/**
* @private

View File

@ -0,0 +1,215 @@
import { Injectable } from '@angular/core';
import { App } from '../components/app/app';
export const enum GesturePriority {
Minimun = -10000,
NavigationOptional = -20,
Navigation = -10,
Normal = 0,
Interactive = 10,
Input = 20,
}
export const enum DisableScroll {
Never,
DuringCapture,
Always,
}
export interface GestureOptions {
disable?: string[];
disableScroll?: DisableScroll;
priority?: number;
}
@Injectable()
export class GestureController {
private id: number = 1;
private requestedStart: { [eventId: number]: number } = {};
private disabledGestures: { [eventName: string]: Set<number> } = {};
private disabledScroll: Set<number> = new Set<number>();
private appRoot: App;
private capturedID: number = null;
create(name: string, opts: GestureOptions = {}): GestureDelegate {
let id = this.id; this.id++;
return new GestureDelegate(name, id, this, opts);
}
start(gestureName: string, id: number, priority: number): boolean {
if (!this.canStart(gestureName)) {
delete this.requestedStart[id];
return false;
}
this.requestedStart[id] = priority;
return true;
}
capture(gestureName: string, id: number, priority: number): boolean {
if (!this.start(gestureName, id, priority)) {
return false;
}
let requestedStart = this.requestedStart;
let maxPriority = GesturePriority.Minimun;
for (let gestureID in requestedStart) {
maxPriority = Math.max(maxPriority, requestedStart[gestureID]);
}
if (maxPriority === priority) {
this.capturedID = id;
this.requestedStart = {};
return true;
}
delete requestedStart[id];
console.debug(`${gestureName} can not start because it is has lower priority`);
return false;
}
release(id: number) {
delete this.requestedStart[id];
if (this.capturedID && id === this.capturedID) {
this.capturedID = null;
}
}
disableGesture(gestureName: string, id: number) {
let set = this.disabledGestures[gestureName];
if (!set) {
set = new Set<number>();
this.disabledGestures[gestureName] = set;
}
set.add(id);
}
enableGesture(gestureName: string, id: number) {
let set = this.disabledGestures[gestureName];
if (set) {
set.delete(id);
}
}
disableScroll(id: number) {
let isEnabled = !this.isScrollDisabled();
this.disabledScroll.add(id);
if (isEnabled && this.isScrollDisabled()) {
// this.appRoot.disableScroll = true;
}
}
enableScroll(id: number) {
let isDisabled = this.isScrollDisabled();
this.disabledScroll.delete(id);
if (isDisabled && !this.isScrollDisabled()) {
// this.appRoot.disableScroll = false;
}
}
canStart(gestureName: string): boolean {
if (this.capturedID) {
// a gesture already captured
return false;
}
if (this.isDisabled(gestureName)) {
return false;
}
return true;
}
isCaptured(): boolean {
return !!this.capturedID;
}
isScrollDisabled(): boolean {
return this.disabledScroll.size > 0;
}
isDisabled(gestureName: string): boolean {
let disabled = this.disabledGestures[gestureName];
if (disabled && disabled.size > 0) {
return true;
}
return false;
}
}
export class GestureDelegate {
private disable: string[];
private disableScroll: DisableScroll;
public priority: number = 0;
constructor(
private name: string,
private id: number,
private controller: GestureController,
opts: GestureOptions
) {
this.disable = opts.disable || [];
this.disableScroll = opts.disableScroll || DisableScroll.Never;
this.priority = opts.priority || 0;
// Disable gestures
for (let gestureName of this.disable) {
controller.disableGesture(gestureName, id);
}
// Disable scrolling (always)
if (this.disableScroll === DisableScroll.Always) {
controller.disableScroll(id);
}
}
canStart(): boolean {
if (!this.controller) {
return false;
}
return this.controller.canStart(this.name);
}
start(): boolean {
if (!this.controller) {
return false;
}
return this.controller.start(this.name, this.id, this.priority);
}
capture(): boolean {
if (!this.controller) {
return false;
}
let captured = this.controller.capture(this.name, this.id, this.priority);
if (captured && this.disableScroll === DisableScroll.DuringCapture) {
this.controller.disableScroll(this.id);
}
return captured;
}
release() {
if (!this.controller) {
return;
}
this.controller.release(this.id);
if (this.disableScroll === DisableScroll.DuringCapture) {
this.controller.enableScroll(this.id);
}
}
destroy() {
if (!this.controller) {
return;
}
this.release();
for (let disabled of this.disable) {
this.controller.enableGesture(disabled, this.id);
}
if (this.disableScroll === DisableScroll.Always) {
this.controller.enableScroll(this.id);
}
this.controller = null;
}
}

View File

@ -0,0 +1,314 @@
import { GestureController, DisableScroll } from '../../../src';
export function run() {
it('should create an instance of GestureController', () => {
let c = new GestureController();
expect(c.isCaptured()).toEqual(false);
expect(c.isScrollDisabled()).toEqual(false);
});
it('should test scrolling enable/disable stack', () => {
let c = new GestureController();
c.enableScroll(1);
expect(c.isScrollDisabled()).toEqual(false);
c.disableScroll(1);
expect(c.isScrollDisabled()).toEqual(true);
c.disableScroll(1);
c.disableScroll(1);
expect(c.isScrollDisabled()).toEqual(true);
c.enableScroll(1);
expect(c.isScrollDisabled()).toEqual(false);
for (var i = 0; i < 100; i++) {
for (var j = 0; j < 100; j++) {
c.disableScroll(j);
}
}
for (var i = 0; i < 100; i++) {
expect(c.isScrollDisabled()).toEqual(true);
c.enableScroll(50 - i);
c.enableScroll(i);
}
expect(c.isScrollDisabled()).toEqual(false);
});
it('should test gesture enable/disable stack', () => {
let c = new GestureController();
c.enableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(false);
c.disableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(true);
c.disableGesture('swipe', 1);
c.disableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(true);
c.enableGesture('swipe', 1);
expect(c.isDisabled('swipe')).toEqual(false);
// Disabling gestures multiple times
for (var gestureName = 0; gestureName < 10; gestureName++) {
for (var i = 0; i < 50; i++) {
for (var j = 0; j < 50; j++) {
c.disableGesture(gestureName.toString(), j);
}
}
}
for (var gestureName = 0; gestureName < 10; gestureName++) {
for (var i = 0; i < 49; i++) {
c.enableGesture(gestureName.toString(), i);
}
expect(c.isDisabled(gestureName.toString())).toEqual(true);
c.enableGesture(gestureName.toString(), 49);
expect(c.isDisabled(gestureName.toString())).toEqual(false);
}
});
it('should test if canStart', () => {
let c = new GestureController();
expect(c.canStart('event')).toEqual(true);
expect(c.canStart('event1')).toEqual(true);
expect(c.canStart('event')).toEqual(true);
expect(c['requestedStart']).toEqual({});
expect(c.isCaptured()).toEqual(false);
});
it('should initialize a delegate without options', () => {
let c = new GestureController();
let g = c.create('event');
expect(g['name']).toEqual('event');
expect(g.priority).toEqual(0);
expect(g['disable']).toEqual([]);
expect(g['disableScroll']).toEqual(DisableScroll.Never);
expect(g['controller']).toEqual(c);
expect(g['id']).toEqual(1);
let g2 = c.create('event2');
expect(g2['id']).toEqual(2);
});
it('should initialize a delegate with options', () => {
let c = new GestureController();
let g = c.create('swipe', {
priority: -123,
disableScroll: DisableScroll.DuringCapture,
disable: ['event2']
});
expect(g['name']).toEqual('swipe');
expect(g.priority).toEqual(-123);
expect(g['disable']).toEqual(['event2']);
expect(g['disableScroll']).toEqual(DisableScroll.DuringCapture);
expect(g['controller']).toEqual(c);
expect(g['id']).toEqual(1);
});
it('should test if several gestures can be started', () => {
let c = new GestureController();
let g1 = c.create('swipe');
let g2 = c.create('swipe1', {priority: 3});
let g3 = c.create('swipe2', {priority: 4});
for (var i = 0; i < 10; i++) {
expect(g1.start()).toEqual(true);
expect(g2.start()).toEqual(true);
expect(g3.start()).toEqual(true);
}
expect(c['requestedStart']).toEqual({
1: 0,
2: 3,
3: 4
});
g1.release();
g1.release();
expect(c['requestedStart']).toEqual({
2: 3,
3: 4
});
expect(g1.start()).toEqual(true);
expect(g2.start()).toEqual(true);
g3.destroy();
expect(c['requestedStart']).toEqual({
1: 0,
2: 3,
});
});
it('should test if several gestures try to capture at the same time', () => {
let c = new GestureController();
let g1 = c.create('swipe1');
let g2 = c.create('swipe2', { priority: 2 });
let g3 = c.create('swipe3', { priority: 3 });
let g4 = c.create('swipe4', { priority: 4 });
let g5 = c.create('swipe5', { priority: 5 });
// Low priority capture() returns false
expect(g2.start()).toEqual(true);
expect(g3.start()).toEqual(true);
expect(g1.capture()).toEqual(false);
expect(c['requestedStart']).toEqual({
2: 2,
3: 3
});
// Low priority start() + capture() returns false
expect(g2.capture()).toEqual(false);
expect(c['requestedStart']).toEqual({
3: 3
});
// Higher priority capture() return true
expect(g4.capture()).toEqual(true);
expect(c.isScrollDisabled()).toEqual(false);
expect(c.isCaptured()).toEqual(true);
expect(c['requestedStart']).toEqual({});
// Higher priority can not capture because it is already capture
expect(g5.capture()).toEqual(false);
expect(g5.canStart()).toEqual(false);
expect(g5.start()).toEqual(false);
expect(c['requestedStart']).toEqual({});
// Only captured gesture can release
g1.release();
g2.release();
g3.release();
g5.release();
expect(c.isCaptured()).toEqual(true);
// G4 releases
g4.release();
expect(c.isCaptured()).toEqual(false);
// Once it was release, any gesture can capture
expect(g1.start()).toEqual(true);
expect(g1.capture()).toEqual(true);
});
it('should destroy correctly', () => {
let c = new GestureController();
let g = c.create('swipe', {
priority: 123,
disableScroll: DisableScroll.Always,
disable: ['event2']
});
expect(c.isScrollDisabled()).toEqual(true);
// Capturing
expect(g.capture()).toEqual(true);
expect(c.isCaptured()).toEqual(true);
expect(g.capture()).toEqual(false);
expect(c.isScrollDisabled()).toEqual(true);
// Releasing
g.release();
expect(c.isCaptured()).toEqual(false);
expect(c.isScrollDisabled()).toEqual(true);
expect(g.capture()).toEqual(true);
expect(c.isCaptured()).toEqual(true);
// Destroying
g.destroy();
expect(c.isCaptured()).toEqual(false);
expect(g['controller']).toBeNull();
// it should return false and not crash
expect(g.start()).toEqual(false);
expect(g.capture()).toEqual(false);
g.release();
});
it('should disable some events', () => {
let c = new GestureController();
let goback = c.create('goback');
expect(goback.canStart()).toEqual(true);
let g2 = c.create('goback2');
expect(g2.canStart()).toEqual(true);
let g3 = c.create('swipe', {
disable: ['range', 'goback', 'something']
});
let g4 = c.create('swipe2', {
disable: ['range']
});
// it should be noop
g3.release();
// goback is disabled
expect(c.isDisabled('range')).toEqual(true);
expect(c.isDisabled('goback')).toEqual(true);
expect(c.isDisabled('something')).toEqual(true);
expect(c.isDisabled('goback2')).toEqual(false);
expect(goback.canStart()).toEqual(false);
expect(goback.start()).toEqual(false);
expect(goback.capture()).toEqual(false);
expect(g3.canStart()).toEqual(true);
// Once g3 is destroyed, goback and something should be enabled
g3.destroy();
expect(c.isDisabled('range')).toEqual(true);
expect(c.isDisabled('goback')).toEqual(false);
expect(c.isDisabled('something')).toEqual(false);
expect(g3.canStart()).toEqual(false);
// Once g4 is destroyed, range is also enabled
g4.destroy();
expect(c.isDisabled('range')).toEqual(false);
expect(g4.canStart()).toEqual(false);
});
it('should disable scrolling on capture', () => {
let c = new GestureController();
let g = c.create('goback', {
disableScroll: DisableScroll.DuringCapture,
});
let g1 = c.create('swipe');
g.start();
expect(c.isScrollDisabled()).toEqual(false);
g1.capture();
g.capture();
expect(c.isScrollDisabled()).toEqual(false);
g1.release();
expect(c.isScrollDisabled()).toEqual(false);
g.capture();
expect(c.isScrollDisabled()).toEqual(true);
let g2 = c.create('swipe2', {
disableScroll: DisableScroll.Always,
});
g.release();
expect(c.isScrollDisabled()).toEqual(true);
g2.destroy();
expect(c.isScrollDisabled()).toEqual(false);
g.capture();
expect(c.isScrollDisabled()).toEqual(true);
g.destroy();
expect(c.isScrollDisabled()).toEqual(false);
});
}