Input Events Management
Principle
All UIs for TV are supposed to be keyboard driven. Hence, all navigation by key event that are dispatched to screen and then to views. For specific remote (e.g LG magic remote), check // TODO link.
Expected Use
AppScreen
The AppScreen is the screen from which all the screens of the application should inherit. It should implement the $MNavigation
traits to offers the left, right up and down to all the screens.
import $Screen from "@Screen";
import $MNavigation from "@MNavigation";
/**
* The screen from which all other screens inherit
*
* @name AppScreen
* @class
* @extends Screen
*/
export default $Screen.declare("AppScreen", {
traits: [$MNavigation],
inputs: ["keypress.UP", "keypress.DOWN", "keypress.LEFT", "keypress.RIGHT"],
methods: /** @lends AppScreen.prototype */ {
//////////////////////////////////////////////////////////////////////////////////////////
// CALLBACKS
//////////////////////////////////////////////////////////////////////////////////////////
onKeypressLEFT: function (key) {
this.left(key);
},
onKeypressRIGHT: function (key) {
this.right(key);
},
onKeypressUP: function (key) {
this.up(key);
},
onKeypressDOWN: function (key) {
this.down(key);
},
//////////////////////////////////////////////////////////////////////////////////////////
// NAVIGATION
//////////////////////////////////////////////////////////////////////////////////////////
/**
* @override
*/
left: function (key) {
return this.focusedChild.hasNavigation ? this.focusedChild.left(key) : false;
},
/**
* @override
*/
right: function (key) {
return this.focusedChild.hasNavigation ? this.focusedChild.right(key) : false;
},
/**
* @override
*/
up: function (key) {
return this.focusedChild.hasNavigation ? this.focusedChild.up(key) : false;
},
/**
* @override
*/
down: function (key) {
return this.focusedChild.hasNavigation ? this.focusedChild.down(key) : false;
}
}
});
Screen
In this example, the screen has two menus, viewA and viewB. When viewA is focused and user press down or up, viewB is focused. When viewB is focused and user press down or up, viewA is focused. The right and left navigation is handled automatically as the $MenuView
is an horizontal list view ($ListViewwith$MHListNavigation
).
Example
import $AppScreen from '@AppScreen';
import $MenuView from "@MenuView";
/**
* The main menu screen
*
* @name MenuScreen
* @class
* @extends AppScreen
* @screen
*/
export default $AppScreen.declare("MenuScreen", {
children: {
viewA: {class: $MenuView},
viewB: {class: $MenuView}
},
inputs: ["keypress.OK"],
methods: /** @lends MenuScreen.prototype */ {
//////////////////////////////////////////////////////////////////////////////////////////
// NAVIGATION
//////////////////////////////////////////////////////////////////////////////////////////
ok: function () {
if (this.focusedChild.id === this.viewA.id) {
this.viewB.focus();
} else {
this.route(this.viewB.getItem());
}
},
/**
* @override
*/
up: function (key) {
if (this.focusedChild.id === this.viewA.id) {
this.viewB.focus();
return true;
} else {
return false;
}
},
/**
* @override
*/
down: function (key) {
if (this.focusedChild.id === this.viewA.id) {
this.viewB.focus();
return true;
} else {
return false;
}
}
}
});
View
If a view is navigable, it must use MNavigation
and implements the left, right up or down methods depending on its needs. Each method must return true if the view has something to do (internal navigation), false if the view has nothing to do (out of bound, …)
Navigation (left, right, up, down)
Use MNavigation on a View that is navigable by keyboard or remote-control:
- Offers the
left
,right
up
anddown
methods that must be implemented - Each methods must return true if the view has something to do (internal navigation), false if the view has nothing to do (out of bound, …)
Example
import $View from "@View";
import $MNavigation from "@MNavigation";
import $TextPrimitive from "@TextPrimitive";
/**
* +------------------------------+
* | A |
* | B C |
* | D |
* +------------------------------+
*
*/
export default $View.declare("View", {
trait: [$MNavigation],
children: {
A: { class: $TextPrimitive, text: "A" },
B: { class: $TextPrimitive, text: "B" },
C: { class: $TextPrimitive, text: "C" },
D: { class: $TextPrimitive, text: "D" }
},
states: {
"A": { "A.color": "#aaaaaa" },
"B": { "B.color": "#aaaaaa" },
"C": { "C.color": "#aaaaaa" },
"D": { "D.color": "#aaaaaa" },
},
style: {
A: { color: "#ffffff"},
B: { color: "#ffffff"},
C: { color: "#ffffff"},
D: { color: "#ffffff"}
},
methods: {
//////////////////////////////////////////////////////////////////////////////////////////
// NAVIGATION
//////////////////////////////////////////////////////////////////////////////////////////
/**
* @override
*/
up: function (key) {
if (this.inState("A")) {
// out of bound, no internal navigation
return false;
} else {
//state B, C, D => up goes to A
this.setState("A");
return true;
}
},
down: function (key) {
if (this.inState("D")) {
// out of bound, no internal navigation
return false;
} else {
// state B, C, A => down goes to D
this.setState("D");
return true;
}
},
left: function (key) {
if (this.inState("B")) {
// out of bound, no internal navigation
return false;
} else if (this.inState("C")) {
// state C => left goes to A
this.setState("A");
return true;
} else {
// state A, D => left goes to B
this.setState("B");
return true;
}
},
right: function (key) {
if (this.inState("C")) {
// out of bound, no internal navigation
return false;
} else if (this.inState("B")) {
// state B => right goes to A
this.setState("A");
return true;
} else {
// state A, D => right goes to C
this.setState("C");
return true;
}
}
}
})
Configuration
You can configure the key mapping and behaviour through the profile (app.config.json):
{
"default":{
"KeyEventAdapter": {
"eventDelayInterval": 500,
"longKeyPressNbEvents": 2,
"eventDelayInterval": 100
},
"KeyMapping": {
"BACK": 35,
"TRIANGLE": 42
}
}
}
The following parameters are available for the key adapter:
parameter | type | description | default |
---|---|---|---|
eventDelayInterval | number | minimum interval between two events (in millisecond) (0 => deactivated) | 0 |
longKeyPressNbEvents | number | number of ‘keyPress’ event that represent a long key press | Β 2 |
longKeyPressTimeout | number | delay before to trigger a long key press event | Β 0Β |
For key mapping configuration, all keys must reference $KeyConst
static variables. The associated value is the native key event code to map.
Advanced use
Action
An action is triggered when the user press a key that is not dedicated to navigation. All actions are handled by the screen because it is the only one that can route to another screen, consumes services or whatever else.
Below find a simple example of an the management of an action triggered on “ok” redirect and passing the content.id
property from one screen to another.
export default $AppScreen.declare("VodScreen", {
methods: /** @lends VodScreen.prototype */ {
/**
* @override
*/
ok: function (key) {
if (this.focusedChild.id === this.vodRail.id) {
const content = this.vodRail.getItem();
return this.route("anotherScreen", {
contentId: content.id,
});
}
}
},