Trackpad input 10.3.1

The BlackBerry Classic smartphone introduces optical trackpad input, which is a new input method that you can support in your apps. The trackpad uses an infrared sensor to detect the displacement of a finger moving over top of it and translates that input into directional movements on the screen.

Cascades allows you to customize the behavior of trackpad navigation in a number of ways: you can set which control gets focus initially, customize the navigation path, and even change the highlight style. If you need more control over the trackpad, you can also handle trackpad events explicitly.

The BlackBerry Classic also introduces four physical keys: the Call, Menu, Back, and End keys. To learn more about customizing the behavior of these keys, see BlackBerry Classic navigation keys.

Trackpad navigation

Basic support for trackpad navigation doesn't require any code changes in your apps. While the user moves their finger over the trackpad, the focus highlight moves from one control to another as specified by the navigation controller. For example, when the user swipes left, focus moves to the control to the immediate left of the previous control. The focus highlight color is derived from the brand color that your app uses.

The image to the right shows how trackpad navigation works in the starshipsettings app. Initial focus is set on the hyperdrive option, and when the user scrolls down, focus moves down the screen from control to control. Some controls, such as the image and labels, don't receive focus by default. When the user presses the trackpad with the toggle button in focus, the toggle button is turned on.

An app screen shot that demonstrates how navigation works using a BlackBerry Classic smartphone.

You can specify which controls can receive focus, and the order in which the controls receive focus. In this example, it might not make sense to give the hyperdrive option focus initially because it's already turned on by default. A preferred option might be to change the initial focus to a control that you use more often, such as the Uranus scanner. To change the initial focus within the current scope, you can set the locallyFocused property for the control to true.

Enabling or disabling focus

Some controls can get focus by default and others cannot get focus. You might want to change the default behavior to suit you app's needs.

For example, consider the weatherguesser sample app in the image to the right. When the app loads, the first weather item in the list receives focus.

This behavior doesn't make sense for this app, because the list item includes only images and labels and doesn't include anything for the user to interact with.

A screen shot of the weatherguesser app that shows focus on the first list item.

By default, containers, labels, and images don't get focus, except in cases where they're used in a list item. In a list item, the root control can get focus by default.

To change the focus policy for a control, use the NavigationFocusPolicy enum. In the example above, you must set the NavigationFocusPolicy for the ListView and the root control of each of its list items to NotFocusable to prevent the list from receiving focus. Preventing the list from receiving focus causes the app to give focus to the TabbedPane, because there aren't any other controls on the screen that can receive focus.

A screen shot that of the weatherguesser sample that shows focus on the tabbed pane.

Here's an example of how to change a control's focus policy.

import bb.cascades 1.4

Container {
    Button {
        text: "Enough with the focus!"    
        navigation.focusPolicy: NavigationFocusPolicy.NotFocusable
    }
    Label {
        text: "Focus, please."
        navigation.focusPolicy: NavigationFocusPolicy.Focusable
    }
}
    // Prevents the button from receiving focus
    Button *button = Button::create();
    Navigation *nav = button->navigation();
    nav->setFocusPolicy(NavigationFocusPolicy::NotFocusable);

    // Allows the label to receive focus
    Label *label = Button::create();
    Navigation *nav2 = label->navigation();
    nav2->setFocusPolicy(NavigationFocusPolicy::Focusable);

Not applicable

Changing the navigation path

Every app has a default path that is followed when the user moves the trackpad, but you can override the behavior and plot your own navigation path. If you override the navigation path, make sure that the navigation is logical and the user doesn't jump around unexpectedly. To use trackpad navigation effectively, see Trackpad and Classic navigation keys .

To customize the navigation path, you can use the Navigation class to specify which directions focus can move in (upAllowed, downAllowed, leftAllowed, rightAllowed) and what the next control in the path is (up, down, left, right).

The image below displays a basic app with that has four labels that can get and no changes to the default navigation path.

When the user moves a finger on the trackpad, the navigation path might seem to behave inconsistently. When you move your finger up and down across the trackpad, focus always moves to the Left or Right control before it reaches its final destination at the top or bottom. However, when you move your finger to the left or right, focus doesn't move to the Top or Bottom control before it moves to the other side; it moves straight across.

An animation that demonstrates how navigation moves around the screen.

This behavior is by design, but depending on your app, it might be something that you want to change.

Here's how you can customize the navigation so that regardless of which control currently has focus. A swipe gesture in any direction gives focus to the control on that edge of the display.

import bb.cascades 1.4

Page {
    Container {
        layout: DockLayout {}
        Label {
            id: left_label
            horizontalAlignment: HorizontalAlignment.Left
            verticalAlignment: VerticalAlignment.Center
            textStyle.fontSize: FontSize.XLarge
            text: "Left"
            navigation {
                focusPolicy: NavigationFocusPolicy.Focusable
                up: top_label
                down: bottom_label
                right: right_label 
            }
        }
        Label {
            id: right_label
            horizontalAlignment: HorizontalAlignment.Right
            verticalAlignment: VerticalAlignment.Center
            textStyle.fontSize: FontSize.XLarge
            text: "Right"
            navigation {
                focusPolicy: NavigationFocusPolicy.Focusable
                up: top_label
                down: bottom_label
                left: left_label
            }
        }
        Label {
            id: top_label
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Top
            textStyle.fontSize: FontSize.XLarge
            text: "Top"
            navigation {
                focusPolicy: NavigationFocusPolicy.Focusable
                down: bottom_label
                right: right_label
                left: left_label
            }
        }
        Label {
            id: bottom_label
            horizontalAlignment: HorizontalAlignment.Center
            verticalAlignment: VerticalAlignment.Bottom
            textStyle.fontSize: FontSize.XLarge
            text: "Bottom"
            navigation {
                focusPolicy: NavigationFocusPolicy.Focusable
                up: top_label
                right: right_label
                left: left_label
            }
        }
    }
}

Coming soon!

Not applicable

Custom highlighting

In some cases, the default highlighting effect might not be suitable for parts of your app, particularly if your app includes custom controls. Here's an example of a custom control that could be used as a contact card. The control is made up of a profile image, a few labels, an icon indicating whether the contact is blocked or approved, and containers for grouping all the children.

A screen shot that displays two contact cards.

Although none of the child controls (labels and images) offer opportunities for interaction, you might still want to enable focus on the entire contact card to allow for actions like sending the contact a message, or viewing additional details. As with other controls, you can change the focus policy through the container's navigation property. Here's what the contact card looks like with focus enabled.

A screen shot that displays a contact card receiving focus.

The focus highlight technically works but it also has a negative effect on the appearance of the text and icon; their green color doesn't stand out well against the new background. This is poor design and might not be accessible for people with vision impairments. As a best practice, you should listen for changes to a custom control's wantsHighlight property and update the appearance of its children accordingly. The wantsHighlight property is a boolean value that indicates whether a control has focus.

The simplest option for changing the green text is to set it to the default text color when the contact card receives focus. For the image, you need to use a BrightnessEffect to increase the brightness of the image until it matches the surrounding text (parity with the default text color occurs using a value of 150).

If you run the app using the bright theme, this level of brightness causes the text to become invisible on a white background, so make sure that you check the app's theme before you use these types of effects. For bright themes, use a value of -150 instead.

Here's what the contact card now looks like now when it receives focus. The green text and icon are now the same color as the rest of the text.

A screen shot that displays a contard card getting focus, with its green icon and text changed to white.

Although the focus highlight color is derived from the brand color that your app uses, you can override the color if you want. Using the contact card example, you might want to use a different highlight for contacts that are currently blocked. There are a few different options for changing the highlight color in this example. One approach is to set the background of the root container to a different color when the contact card gets focus (same approach as changing the green text). Here's what the contact card looks like when you set the background to red.

A screen shot displaying the contact card with a red background.

The problem with changing the background color of the container is that it doesn't affect any of the other controls; the image in the contact card still has the same blue overlay as the previous card. To remove the default highlight effects, you must also set the control's defaultHighlightEnabled property to false. Here's the contact card with default highlighting disabled.

A screen shot displaying the contact card with a bright red background.

If you prefer to keep the default highlight effects, you can create a FocusHighlightEffect for the contact card. The FocusHighlightEffect allows you to set a baseColor and a style for the effect. The app uses the input to create a custom highlight that behaves just like the default highlight. Here's the same contact card with a red FocusHighlightEffect for only the image and with defaultHighlightEnabled set to false.

A screen shot displaying the contact card with a bright red background and a highlight effect on the profile image.

Here's a code sample that demonstrates how to create the contact card control and implement some of the highlighting strategies described above.

// main.qml

import bb.cascades 1.4

Page {
    Container {
        ContactCard {
            contactImage: "asset:///baby_mike.png"
            name: "Mike Chepesky"
            title: "Beer aficionado"
            statusImage: "asset:///online.png"
            blocked: false
        }
        ContactCard {
            contactImage: "asset:///baby_wes.png"
            name: "Wes Barichak"
            title: "Town drunk"
            statusImage: "asset:///blocked.png"
            blocked: true
        }
    }
}
// ContactCard.qml

import bb.cascades 1.4

Container {
    id: root
    property string contactImage: null
    property string name: null
    property string title: null
    property string statusImage: null
    property bool blocked: null

    leftPadding: 10
    rightPadding: 10
    topPadding: 10
    bottomPadding: 10
    preferredWidth: maxWidth
    layout: DockLayout {}
    
    // Allows the root container to be able to receive focus
    navigation.focusPolicy: NavigationFocusPolicy.Focusable
    navigation.defaultHighlightEnabled: blocked && navigation.wantsHighlight ? false : true
    
    // For users that are 'blocked' the background is set to red when it receives focus
    background: blocked && navigation.wantsHighlight ? Color.Red : ui.palette.background
    
    // The controls for the contact card
    Container {
        horizontalAlignment: HorizontalAlignment.Left
        layout: StackLayout {
            orientation: LayoutOrientation.LeftToRight
        }
        ImageView {
            imageSource: contactImage
            rightMargin: 20
            
            // A red focus effect that is used for blocked contacts
            effects: [
                FocusHighlightEffect {
                    enabled: blocked && navigation.wantsHighlight
                    baseColor: Color.Red
                    style: FocusHighlightEffectStyle.Flat
                }
            ]
        }
        Container {
            Container {
                layout: StackLayout {
                    orientation: LayoutOrientation.LeftToRight
                }
                Label {
                    text: name + ","
                }
                Label {
                    text: title
                    verticalAlignment: VerticalAlignment.Center
                    textStyle {
                        fontSize: FontSize.XXSmall
                        fontStyle: FontStyle.Italic
                    }
                }
            }
            Label {
                text: "Online"
                textStyle {
                    fontSize: FontSize.XXSmall
                    
                    // When the card receives focus green text is changed to the default color
                    color: root.navigation.wantsHighlight ? ui.palette.text: Color.DarkGreen
                }
            }
        }
    }
    ImageView {
        imageSource: statusImage
        scalingMethod: ScalingMethod.None
        horizontalAlignment: HorizontalAlignment.Right
        
        // When the card receives focus the icon's color changes to white or black
        // depending on the current theme.
        effects: [
            BrightnessEffect {
                enabled: root.navigation.wantsHighlight
                brightness: { Application.themeSupport.theme.colorTheme.style == 
                    VisualStyle.Bright ? -150 : 150 }
            }
        ]
    }
}

Coming soon!

Not applicable

Trackpad events

Trackpad events occur when a finger moves on the trackpad and when the trackpad button is pressed.

To implement your own trackpad support, you can use event data from the trackpad. Screen exposes trackpad events as SCREEN_EVENT_JOYSTICK events. By default, this event type delivers minimally filtered events that are not adjusted based on the dpi of the device display. Depending on how you use these events in your app, you'll need to do additional filtering and scaling of values to use them effectively. SCREEN_EVENT_JOYSTICK also supports a second mode called SCREEN_INPUT_MODE_POINTER. In this mode, event values are adjusted based on the dpi.

Cascades exposes minimally filtered trackpad events through the TrackpadHandler and TrackpadEvent classes. For more usable trackpad events in Cascades, it's recommended that you use NavigationHandler with NavigationEvent instead. These events are filtered and scaled to accelerate fast swipes and still provide precision control for slower movements on the trackpad. Each NavigationEvent specifies the type of the trackpad event that occurs (left, right, up, down, or cancel).

To avoid hardware-specific dependencies, it's recommended that apps always use the high-level NavigationEvent instead of minimally filtered trackpad events. The current generation of trackpad hardware is designed with a sensitivity of approximately 500 CPI but this is subject to change as new hardware is introduced. A decrease in effective CPI may be observed depending on the surface being tracked.

Here are some examples of how to receive trackpad events in your app.

import bb.cascades 1.4

Page {
    Container {
        id: theContainer
        focusPolicy: FocusPolicy.Touch
        background: Color.Blue
        
        // Requests focus for the container so that events can start
        // being delivered.
        onCreationCompleted: { 
            requestFocus(); 
        }
        
        eventHandlers: [
            NavigationHandler {
                onNavigation: {
                    // Prints out the type of event that occurs. You can use
                    // this value to determine where to navigate in the app
                    console.log("NavigationEvent: " + event.navigationEventType)
                }                    
            } 
        ]
    }
}

Coming soon

// The first section is a stripped-down event handling loop.
while (!program_exit) }
    int domain;
    bps_event_t *event = NULL;
    if (BPS_SUCCESS != bps_get_event(&event, -1)) { // failed to get event }

    if (event) {
        domain = bps_event_get_domain(event);
        if (domain == screen_get_domain()) {
            handle_screen_event();
        }
    }
}

// The handle_screen_event() function handles all screen events.
// This sample demonstrates how to get minimally filtered
// trackpad information.
static void handle_screen_event(bps_event_t *event) {
    int screen_val;
    int displacement[2];
    
    screen_event_t screen_event = screen_event_get_event(event);
    screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &screen_val);
    
    switch(screen_val) {
        // Trackpad events are of type SCREEN_EVENT_JOYSTICK. 
        case SCREEN_EVENT_JOYSTICK:
            // Get the horizontal and vertical displacement values.
            if (0 != screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_DISPLACEMENT, displacement)) {
                // Handle failure to get property.
            }

            // Get whether the trackpad button is pushed down (1), or up (0).
            if (0 != screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_BUTTONS, &buttons)) {
                // Handle failure to get buttons.
            }
        default:
            break;
    }            
    return;
}

Last modified: 2015-05-07



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus