This Library has been build to bring the capabilities of Lightning
to Automotive Ui's and Apps. This Library will
track each individual finger and analyses it's intent. Once the analyser recognizes a gesture it tries to dispatch
that event on one of the active touched elements.
Update your project's dependency and run npm install
"dependencies": {
"@lightningjs/automotive": "git+ssh://git@github.com/mlapps/Lightning-Automotive.git",
}
https://github.com/mlapps/com.metrological.ui.Automotive
If you want to enrich your app with Automotive touch-support you first import the Library into your app.
import {Automotive} from '@lightningjs/automotive'
Next step is to start the automotive engine, in this example we implement it into a Routed App, and start
it
right after the component get's attached to the render-tree ( _setup
lifecycle event)
export default class App extends Router.App {
_setup(){
Automotive.start(
this.application, settings
)
}
}
There are 2 main types that provide a lot of information on the started / active and ended touch event.
Every touchstart
will start the creation of a new Record
that will be available and update during the lifespan of
the current touch action (press / swipe / drag pinch etc.)
Interface Record {
startime: number;
endtime: number;
fingers: Map;
fingersTouched: number;
duration: number;
isTap: boolean;
isHold: boolean;
moved: boolean;
hasFingerMoved: ()=> boolean;
startposition: Vector;
delta: Vector;
firstFinger: Finger;
analyzed:boolean;
}
This library tracks each individual Finger
that it pressing the screen. Each Record
stores a Map
with data for every finger
that's currently on the screen and will keep track of their current position. By doing this, the library can make a distinction on
a Swipe
with one or multiple fingers. While building your App or Ui you can call recordInstance.fingers
getter to receive the
Map
with with each Finger
instance.
Interface Finger {
moved: boolean;
identifier: number;
start: Vector;
end: Vector;
position: Vector;
delta: Vector;
queue: {position:Vector, time: number}[];
pinching: boolean;
}
When one or more Finger
's are touching the screen this library will try to analyze it's intent. This library ships
support for the following gestures:
- Single tap
- Double tap
- Multi-finger tap
- Single longpress
- Multi-finger longpress
- Pinch (zoom and rotation)
- Spread (zoom and rotation)
- Swipe left
- Swipe right
- Swipe up
- Swipe down
- Multi-finger directional swipe
This library tries to recognize one (or more) of this gestures when a Record
ing has ended or when one more Finger
s
are moving across the screen. Once a gesture is recognized the touch ending will search for all the on-screen Components
that are rendered at the position where one of your fingers is positioned. When collision is detected it will order
the Components
by z-index (so highest order first) and try to call a corresponding Event
on that component. If a Component
is not handling that specific event it will try to call it on the lower positioned (z-index) Component
.
This will enable you to add touch life-cycle events on i.e: a List Component
but still render the Items
on a higher level.
In addition to Lightning's lifecycle events, this Library provide a set of new events that you can add to your Component
to enable and listen to touch
behaviour. Each function
will have 2 parameters:
The active Record
as described above. Important to know that this recording can still be updated if the recording
has not ended.
An Object
that provides that provides position local to the Component
that has been touched
(as the recording only provides world position)
type Local = {
first: Vector;
all: Map<fingerId, Vector>;
}
first
is a Vector
with local positions for the first finger that touched the Component
and all
holds
a Map
with local positions for each finger that's touching the screen.
Will be called when one finger quickly touches this element
_onSingleTap(recording, local){ }
Will be called when mutliple fingers quickly touches this element
_onMultiTap(recording, local){ }
When one finger quickly double taps the same element
_onDoubleTap(recording, local){ }
Will be invoked if one or more fingers are pressing this element for < 800ms. For now the recording data holds data for all the fingers so it could be that 3 fingers are touching 3 individual elements they all receive
_onLongpress(recording, local){ }
Will be invoked when you start dragging an element
_onDragStart(recording, local){ }
Will be invoked when you touch an element and start moving your finger
_onDrag(recording, local){ }
When you stop dragging an element
_onDragEnd(recording, local){ }
When one of more fingers perform a swipe to the left (moving along the x-axis where startposition > endposition)
_onSwipeLeft(recording, local){ }
When one of more fingers perform a swipe to the right (moving along the x-axis where startposition < endposition)
_onSwipeRight(recording, local){ }
When one of more fingers perform a swipe up (moving along the y-axis where startposition > endposition)
_onSwipeUp(recording, local){ }
When one of more fingers perform a swipe up (moving along the y-axis where startposition < endposition)
_onSwipeDown(recording, local){ }
As noted above, the first parameter
of the method is the Record
instance, this will enable you to do some extra
calculation based on duration / force / amount of fingers. But sometimes you want to listen to (in example) a swipeLeft
but only if the gestures
was performed by 2 fingers.
To prevent this pattern:
_onSwipeLeft(recording){
if(recording.fingersTouched === 2){
// execute some logic
}
}
the library provides an swipe event for multiple finger count:
swipe + finger count + direction
This will be invoked when 2 fingers perform a swipe left on a certain Component
(could als be a Page
)
_onSwipe2fLeft(recording, local){ }
This will be invoked when 4 fingers perform a swipe up.
_onSwipe4Up(recording, local){ }
This will be invoked when 8 fingers perform a swipe to the right
_onSwipe8right(recording, local){ }
There is a certain order of invocation; The engine will first to call the multi finger event before falling back to default version.
So if you perform a swipe up with 4 fingers the engine will first try to invoke swipe4fUp
and if it's not being
handled by any Component
it will try to invoke swipeUp
. This enables you to extract logic for a certain multi-finger gesture.
Start automotive touch engine. app
is a reference to a Lightning.Component
instance
that will be the parent
which the children will be tested for collision detection against
different points.
config
is a settings object that can hold different automotive settings
Automotive.start(app, config)
Prevent one or more events from being called on touched Component
s
// block one
Automotive.block('_onSingleTap')
// block multiple
Automotive.block(['_onMultiTap', '_onLongpress'])
Allow one or more events to be called again if they're currently blocked.
// release one
Automotive.release('_onMultiTap')
// release multiple
Automotive.release(['_onSingleTap', '_onLongpress'])
Place one or more events on a whitelist
. If this list.size > 0 we only allow those events
to pass-through, if the list is empty we allow all events again (unless they're blocked)
// block one
Automotive.lock('_onDoubleTap')
// block multiple
Automotive.lock(['_onSwipeLeft', '_onSwipeRight'])
Remove one or more events from the whitelist
. if the list is empty we allow all events again
(unless they're blocked)
// release one
Automotive.unlock('_onSwipeLeft')
// release multiple
Automotive.unlock(['_onDoubleTap', '_onSwipeRight'])
Will return a new Vector
instance to do additional vector calculation.
const center = Automotive.createVector( 960, 540 )
A small helper function that returns the distance between 2 vectors
const dis = Automotive.distance(finger.start, finger.end);
Perform interpolation between 2 values
// returns 0.5
const v = Automotive.smoothstep(0.0, 0.8, 0.4)
Return the force which the finger did during the last swipe in the same direction along the x-axis. by dividing the traveled distance between the swipe duration. For performance reasons we only store the last 70 positions per finger.
const force = Automotive.getHorizontalForce(finger);
Return the force which the finger did during the last swipe in the same direction along the y-axis. by dividing the traveled distance between the swipe duration. For performance reasons we only store the last 70 positions per finger.
const force = Automotive.getVerticalForce(finger);
The amount of milliseconds we keep the 'bridge' open for new new fingers to touch the screen. All touches within the list while the bridge is open will be recorded.
Max Amount of milliseconds between touchstart / end to be flagged
Flag if the Ui needs to listen to doubleTap
(the benefit for disabling is a more snappy Ui since a tap can immediately be invoked)
Max amount of milliseconds that a touchstart can start after a tap flag to be flagged as a double tap
Max distance between 2 taps to be flagged as double tap
Are you using an external touchscreen. This feature has been specially added for beetronics screens under OSX.
Define if components an block global events by adding it to itself as class member
Amount of milliseconds that need to be passed to start flagging recording as a hold; for drag / pinch / long hold
Minimal amount of pixel one or more fingers need to travel before it's gets recognized as a swipe along the x-axis
Minimal amount of pixel one or more fingers need to travel before it's gets recognized as a swipe along the y-axis
Amount of position vectors we store during finger (pointer) movement, a higher number will lead to better precision.
Defaults to 70
For force calculation (i.e getHorizontalForce()
) we divide the distance
a finger has traveled by the
duration
between the first and last touchQueue
element. Due to duration calculation and the fact that
a finger needs to make a small movement to be flagged as moving
, it's possible that we end up with swipe distace
of 0 pixels
and a short duration. In this situation we return the maxForce
Sets the duration
a zero-distance gesture can take to be flagged and return maxForce
const settings = {
bridgeCloseTimeout: 110,
tapDelay: 120,
doubleTapActive: true,
beforeDoubleTapDelay: 180,
flagAsHoldDelay: 800,
doubleTapMaxDistance: 40,
externalTouchScreen: false,
componentBlockBroadcast: true,
swipeXTreshold: 30,
swipeYTreshold: 30,
viewportOffsetX: 0,
viewportOffsetY: 57,
touchQueueMaxLength: 80,
maxForce: 10,
maxZeroDistanceDuration: 50
}