Display Management Flows

On-screen display

Screens and Flows

Displaying information on the devices is done by defining a flow. Think of a flow as a set of screens that will be displayed to the user. The user will be able to navigate the flow using left and right buttons.

The macro UX_FLOW lets you define a flow. It first takes the name of the flow you are defining, and then the different steps of your flow.

Here’s an example:

UX_FLOW(ux_sign_transaction,            // Name of the flow
        &step_destination_address,      // Reference to the first step
        &step_display_amount,           // Reference to second step
        &step_display_transaction_fees, // etc...
        &step_display_approve_step,
        &step_display_reject_step
        );

“But what are those steps? Where can I find them?” you ask? Well, they’re yours to declare in your app! Let’s have a look at how you can declare those.

Declaring steps

A step is declared using one of the macros provided by the SDK. Here’s a list of the most used ones (a more exhaustive list is available by looking at the SDK source code):

  • UX_STEP_NOCB : Your bread and butter step. A step that simply displays information on-screen, without having any callback function (hence the NOCB annotation). This is used to display information to the user (display a public key, display the network name…).
  • UX_STEP_CB : Another widely used step declaration. You need to associate a callback function (hence the CB annotation) that will be called if the user presses both buttons simultaneously. This is used mainly at the end of a flow, to either APPROVE or REJECT a signature for example.
  • FLOW_LOOP : This is a special macro that doesn’t take any arguments. Put it as the last step of your UX_FLOW declaration, and the flow will wrap around: pressing the left button on the first screen will display the last screen, and pressing the right button on the last screen will jump right to the first screen.

Looking back at our previous flow (the ux_display_pubkey_flow), you can guess which steps were declared with UX_STEP_NOCB and UX_STEP_CB:

StepSTEP_MACRONote
ux_display_destination_addressUX_STEP_NOCBThis step will just display the destination address to the user, we’re not expecting him to confirm or reject anything.
ux_display_amountUX_STEP_NOCBThis step will just display the amount to the user, we’re not expecting him to confirm or reject.
ux_display_transaction_feesUX_STEP_NOCBThis step will just display the transaction fees to the user, we’re not expecting him to confirm or reject.
ux_display_approve_stepUX_STEP_CBThis step will prompt the user to CONFIRM that he wishes to sign the transaction. If the user presses both button, the application will call the callback function associated with this step (the code to sign a transaction).
ux_display_reject_stepUX_STEP_CBThis step will prompt the user to REJECT the signature of the transaction. If the user presses both button, the application will call the callback function associated with this step (the code to reject the transaction signature).

Previous example explained

Here’s how the first step is defined:

UX_STEP_NOCB(
    step_destination_address,
    bnnn_paging,
    {
        .title = "Address",
        .text = g_address
    }
);
  • The first argument is the name that we are going to give to this step.
  • The second are what we call a layout option.
  • The third argument goes hand-in-hand with the layout option.

It looks like the layout is where the black-magic happens. Let’s have a closer look at layouts.

Examples

Here’s a typical flow for any app that will display its name (along with its logo), then its version, then the settings and finally a quit (along with a icon).

UX_STEP_NOCB(step_menu, pnn, {&C_app_logo, "App", "is ready"});
/* OR UX_STEP_NOCB(step_menu, nn, {&C_app_logo, "Application", "is ready"}); */
UX_STEP_NOCB(step_version, bn, {"Version", &g_version});
UX_STEP_CB(step_settings, pb, ui_settings_menu(), {&C_icon_settings, "Settings"})
UX_STEP_CB(step_exit_step, pb, os_sched_exit(-1), {&C_icon_dashboard_x, "Quit"});
 
UX_FLOW(ux_app_dashboard,
        &step_menu,
        &step_version,
        &step_settings,
        &step_exit_step,
        FLOW_LOOP
);

Pressing both buttons when on the QUIT screen calls os_sched_exit(-1), effectively quitting the app. Pressing both button while on the Settings screen calls ui_settings_menu(), another function that you need to define with UX_FLOW. We also added the FLOW_LOOP step at the end to have the menu wrap around. Users can now indefinitely cycle through the menu, yay!

Signing a transaction

Here’s the example of a flow to sign a transaction. We first display “Confirm address” along with a picture, then use bnnn_paging to display the address because it might not fit on a single screen. We then display the amount and the transactions fees, and finally add two callback steps: the first one to confirm, the second one to reject.

UX_STEP_NO_CB(step_review, pn, {&C_icon_eye, "Confirm Address"});
UX_STEP_NO_CB(step_address, bnnn_paging, { .title = "Address", .text = &g_destination_address});
UX_STEP_NO_CB(step_amount, bn, {"Amount", &g_amount});
UX_STEP_NO_CB(step_fees, bn, {"Fees", &g_fees});
UX_STEP_CB(step_approve, pb, sign_transaction(), {&C_icon_validate_14, "Approve"});
UX_STEP_CB(step_reject, pb, reject_transaction(), {&C_icon_crossmark, "Reject"});
 
UX_FLOW(ux_sign_transaction,
        &step_review,
        &step_address,
        &step_amount,
        &step_fees,
        &step_approve,
        &step_reject
    );

Advanced display management

The special advanced display management section details more advanced UX_FLOW delcaration. Low-level display management has details about the inner-workings of flows, but feel free to skip this one as it is not mandatory to write new apps.

Ledger
Copyright © Ledger SAS. All rights reserved. Ledger, Ledger Nano S, Ledger Vault, Ledger OS are registered trademarks of Ledger SAS