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, …)

Use MNavigation on a View that is navigable by keyboard or remote-control:

  • Offers the left, right up and down 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:

parametertypedescriptiondefault
eventDelayIntervalnumberminimum interval between two events (in millisecond) (0 => deactivated)0
longKeyPressNbEventsnumbernumber of ‘keyPress’ event that represent a long key pressΒ 2
longKeyPressTimeoutnumberdelay 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,
                });
            }
        }
    },