mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
feat(haptic): add haptic/taptic support to toggle/range/picker
This commit is contained in:
@ -8,6 +8,7 @@ import { Key } from '../../util/key';
|
||||
import { NavParams } from '../../navigation/nav-params';
|
||||
import { Picker } from './picker';
|
||||
import { PickerOptions, PickerColumn, PickerColumnOption } from './picker-options';
|
||||
import { Haptic } from '../../util/haptic';
|
||||
import { UIEventManager } from '../../util/ui-event-manager';
|
||||
import { ViewController } from '../../navigation/view-controller';
|
||||
|
||||
@ -53,12 +54,13 @@ export class PickerColumnCmp {
|
||||
maxY: number;
|
||||
rotateFactor: number;
|
||||
lastIndex: number;
|
||||
lastTempIndex: number;
|
||||
receivingEvents: boolean = false;
|
||||
events: UIEventManager = new UIEventManager();
|
||||
|
||||
@Output() ionChange: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
constructor(config: Config, private elementRef: ElementRef, private _sanitizer: DomSanitizer) {
|
||||
constructor(config: Config, private elementRef: ElementRef, private _sanitizer: DomSanitizer, private _haptic: Haptic) {
|
||||
this.rotateFactor = config.getNumber('pickerRotateFactor', 0);
|
||||
}
|
||||
|
||||
@ -114,6 +116,9 @@ export class PickerColumnCmp {
|
||||
|
||||
this.minY = (minY * this.optHeight * -1);
|
||||
this.maxY = (maxY * this.optHeight * -1);
|
||||
|
||||
this._haptic.gestureSelectionStart();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -146,6 +151,14 @@ export class PickerColumnCmp {
|
||||
}
|
||||
|
||||
this.update(y, 0, false, false);
|
||||
|
||||
let currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0);
|
||||
if (currentIndex !== this.lastTempIndex) {
|
||||
// Trigger a haptic event for physical feedback that the index has changed
|
||||
this._haptic.gestureSelectionChanged();
|
||||
}
|
||||
this.lastTempIndex = currentIndex;
|
||||
|
||||
}
|
||||
|
||||
pointerEnd(ev: UIEvent) {
|
||||
@ -209,6 +222,8 @@ export class PickerColumnCmp {
|
||||
if (isNaN(this.y) || !this.optHeight) {
|
||||
// fallback in case numbers get outta wack
|
||||
this.update(y, 0, true, true);
|
||||
this._haptic.gestureSelectionEnd();
|
||||
|
||||
|
||||
} else if (Math.abs(this.velocity) > 0) {
|
||||
// still decelerating
|
||||
@ -230,12 +245,13 @@ export class PickerColumnCmp {
|
||||
this.velocity = 0;
|
||||
}
|
||||
|
||||
console.log(`decelerate y: ${y}, velocity: ${this.velocity}, optHeight: ${this.optHeight}`);
|
||||
//console.debug(`decelerate y: ${y}, velocity: ${this.velocity}, optHeight: ${this.optHeight}`);
|
||||
|
||||
var notLockedIn = (y % this.optHeight !== 0 || Math.abs(this.velocity) > 1);
|
||||
|
||||
this.update(y, 0, true, !notLockedIn);
|
||||
|
||||
|
||||
if (notLockedIn) {
|
||||
// isn't locked in yet, keep decelerating until it is
|
||||
this.rafId = raf(this.decelerate.bind(this));
|
||||
@ -247,9 +263,17 @@ export class PickerColumnCmp {
|
||||
|
||||
// create a velocity in the direction it needs to scroll
|
||||
this.velocity = (currentPos > (this.optHeight / 2) ? 1 : -1);
|
||||
this._haptic.gestureSelectionEnd();
|
||||
|
||||
this.decelerate();
|
||||
}
|
||||
|
||||
let currentIndex = Math.max(Math.abs(Math.round(y / this.optHeight)), 0);
|
||||
if (currentIndex !== this.lastTempIndex) {
|
||||
// Trigger a haptic event for physical feedback that the index has changed
|
||||
this._haptic.gestureSelectionChanged();
|
||||
}
|
||||
this.lastTempIndex = currentIndex;
|
||||
}
|
||||
|
||||
optClick(ev: UIEvent, index: number) {
|
||||
|
@ -8,6 +8,7 @@ import { Form } from '../../util/form';
|
||||
import { Ion } from '../ion';
|
||||
import { Item } from '../item/item';
|
||||
import { PointerCoordinates, pointerCoord, raf } from '../../util/dom';
|
||||
import { Haptic } from '../../util/haptic';
|
||||
import { UIEventManager } from '../../util/ui-event-manager';
|
||||
|
||||
export const RANGE_VALUE_ACCESSOR: any = {
|
||||
@ -343,6 +344,7 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O
|
||||
|
||||
constructor(
|
||||
private _form: Form,
|
||||
private _haptic: Haptic,
|
||||
@Optional() private _item: Item,
|
||||
config: Config,
|
||||
elementRef: ElementRef,
|
||||
@ -436,6 +438,8 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O
|
||||
this._active.position();
|
||||
this._pressed = this._active.pressed = true;
|
||||
|
||||
this._haptic.gestureSelectionStart();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -473,6 +477,8 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O
|
||||
// update the active knob's position
|
||||
this._active.position();
|
||||
|
||||
this._haptic.gestureSelectionEnd();
|
||||
|
||||
// clear the start coordinates and active knob
|
||||
this._start = this._active = null;
|
||||
this._pressed = this._knobs.first.pressed = this._knobs.last.pressed = false;
|
||||
@ -505,6 +511,12 @@ export class Range extends Ion implements AfterViewInit, ControlValueAccessor, O
|
||||
let newVal = this._active.value;
|
||||
|
||||
if (oldVal !== newVal) {
|
||||
// Trigger a haptic selection changed event if this is
|
||||
// a snap range
|
||||
if (this.snaps) {
|
||||
this._haptic.gestureSelectionChanged();
|
||||
}
|
||||
|
||||
// value has been updated
|
||||
if (this._dual) {
|
||||
this.value = {
|
||||
|
@ -7,6 +7,7 @@ import { isTrueProperty } from '../../util/util';
|
||||
import { Ion } from '../ion';
|
||||
import { Item } from '../item/item';
|
||||
import { pointerCoord } from '../../util/dom';
|
||||
import { Haptic } from '../../util/haptic';
|
||||
import { UIEventManager } from '../../util/ui-event-manager';
|
||||
|
||||
export const TOGGLE_VALUE_ACCESSOR: any = {
|
||||
@ -124,6 +125,7 @@ export class Toggle extends Ion implements AfterContentInit, ControlValueAccesso
|
||||
config: Config,
|
||||
elementRef: ElementRef,
|
||||
renderer: Renderer,
|
||||
public _haptic: Haptic,
|
||||
@Optional() public _item: Item
|
||||
) {
|
||||
super(config, elementRef, renderer);
|
||||
@ -158,12 +160,15 @@ export class Toggle extends Ion implements AfterContentInit, ControlValueAccesso
|
||||
if (this._checked) {
|
||||
if (currentX + 15 < this._startX) {
|
||||
this.onChange(false);
|
||||
this._haptic.selection();
|
||||
this._startX = currentX;
|
||||
this._activated = true;
|
||||
}
|
||||
|
||||
} else if (currentX - 15 > this._startX) {
|
||||
this.onChange(true);
|
||||
// Create a haptic event
|
||||
this._haptic.selection();
|
||||
this._startX = currentX;
|
||||
this._activated = (currentX < this._startX + 5);
|
||||
}
|
||||
@ -180,10 +185,12 @@ export class Toggle extends Ion implements AfterContentInit, ControlValueAccesso
|
||||
if (this.checked) {
|
||||
if (this._startX + 4 > endX) {
|
||||
this.onChange(false);
|
||||
this._haptic.selection();
|
||||
}
|
||||
|
||||
} else if (this._startX - 4 < endX) {
|
||||
this.onChange(true);
|
||||
this._haptic.selection();
|
||||
}
|
||||
|
||||
this._activated = false;
|
||||
|
@ -10,6 +10,7 @@ export * from './gestures/gesture-controller';
|
||||
|
||||
export * from './util/click-block';
|
||||
export * from './util/events';
|
||||
export * from './util/haptic';
|
||||
export * from './util/keyboard';
|
||||
export * from './util/form';
|
||||
export { reorderArray } from './util/util';
|
||||
|
@ -15,6 +15,7 @@ import { DeepLinker, setupDeepLinker } from './navigation/deep-linker';
|
||||
import { Events, setupProvideEvents } from './util/events';
|
||||
import { Form } from './util/form';
|
||||
import { GestureController } from './gestures/gesture-controller';
|
||||
import { Haptic } from './util/haptic';
|
||||
import { IonicGestureConfig } from './gestures/gesture-config';
|
||||
import { Keyboard } from './util/keyboard';
|
||||
import { LoadingController } from './components/loading/loading';
|
||||
@ -52,6 +53,7 @@ import { ToastCmp } from './components/toast/toast-component';
|
||||
*/
|
||||
export { Config, setupConfig, ConfigToken } from './config/config';
|
||||
export { Platform, setupPlatform, UserAgentToken, DocumentDirToken, DocLangToken, NavigatorPlatformToken } from './platform/platform';
|
||||
export { Haptic } from './util/haptic';
|
||||
export { QueryParams, setupQueryParams, UrlToken } from './platform/query-params';
|
||||
export { DeepLinker } from './navigation/deep-linker';
|
||||
export { NavController } from './navigation/nav-controller';
|
||||
@ -163,6 +165,7 @@ export class IonicModule {
|
||||
App,
|
||||
Events,
|
||||
Form,
|
||||
Haptic,
|
||||
GestureController,
|
||||
Keyboard,
|
||||
LoadingController,
|
||||
|
109
src/util/haptic.ts
Normal file
109
src/util/haptic.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Platform } from '../platform/platform';
|
||||
|
||||
declare var window;
|
||||
|
||||
/**
|
||||
* @name Haptic
|
||||
* @description
|
||||
* The `Haptic` class interacts with a haptic engine on the device, if available. Generally,
|
||||
* Ionic components use this under the hood, but you're welcome to get a bit crazy with it
|
||||
* if you fancy.
|
||||
*
|
||||
* Currently, this uses the Taptic engine on iOS.
|
||||
*
|
||||
* @usage
|
||||
* ```ts
|
||||
* export class MyClass{
|
||||
* constructor(haptic: Haptic){
|
||||
* haptic.selection();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
|
||||
@Injectable()
|
||||
export class Haptic {
|
||||
plugin: any;
|
||||
|
||||
constructor(platform: Platform) {
|
||||
platform.ready().then(() => {
|
||||
this.plugin = window.TapticEngine;
|
||||
});
|
||||
}
|
||||
|
||||
available() {
|
||||
return !!this.plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a selection changed haptic event. Good for one-time events (not for gestures)
|
||||
*/
|
||||
selection() {
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.selection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the haptic engine that a gesture for a selection change is starting.
|
||||
*/
|
||||
gestureSelectionStart() {
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.gestureSelectionStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the haptic engine that a selection changed during a gesture.
|
||||
*/
|
||||
gestureSelectionChanged() {
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.gestureSelectionChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the haptic engine we are done with a gesture. This needs to be
|
||||
* called lest resources are not properly recycled.
|
||||
*/
|
||||
gestureSelectionEnd() {
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.gestureSelectionEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to indicate success/failure/warning to the user.
|
||||
* options should be of the type { type: 'success' } (or 'warning'/'error')
|
||||
*/
|
||||
notification(options: { type: string }) {
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.notification(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to indicate success/failure/warning to the user.
|
||||
* options should be of the type { style: 'light' } (or 'medium'/'heavy')
|
||||
*/
|
||||
impact(options: { style: string }) {
|
||||
if(!this.plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.impact(options);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user