mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-23 05:58:26 +08:00
feat(gesture): Introducing new gesture controller
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
import {Gesture} from './gesture';
|
||||
import {defaults} from '../util';
|
||||
import { Gesture } from './gesture';
|
||||
import { defaults } from '../util';
|
||||
|
||||
/**
|
||||
* @private
|
||||
|
215
src/gestures/gesture-controller.ts
Normal file
215
src/gestures/gesture-controller.ts
Normal 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;
|
||||
}
|
||||
}
|
314
src/gestures/test/gesture-controller.spec.ts
Normal file
314
src/gestures/test/gesture-controller.spec.ts
Normal 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);
|
||||
});
|
||||
|
||||
}
|
Reference in New Issue
Block a user