Skip to content

Commit

Permalink
Merge pull request #770 from LedgerHQ/improved-action-button-in-home-…
Browse files Browse the repository at this point in the history
…page

Improve action button in Home Screen
  • Loading branch information
nroggeman-ledger committed Sep 18, 2024
2 parents 73b6dbb + ecf4521 commit 766b9c1
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 189 deletions.
4 changes: 2 additions & 2 deletions lib_nbgl/doc/mainpage.dox
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#ifdef HAVE_SE_TOUCH
/** @page nbgl_mainpage New BOLOS Graphic API for Stax
/** @page nbgl_mainpage New BOLOS Graphic API for Stax/Flex

@section nbgl_mainpage_intro Introduction

This documentation describes the different interfaces of <b>NBGL</b>, the library that
is targeted to be integrated in Stax product.
is targeted to be integrated in Stax or Flex product.

<b>NBGL Engine</b> is responsible for constructing screens and handling Touchscreen input.

Expand Down
83 changes: 31 additions & 52 deletions lib_nbgl/doc/nbgl_use_case.dox
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ A few APIs are available to draw typical Use-Cases, such as:
- @ref nbgl_useCaseReviewLight() to draw the pages of a transaction review with a simple button confirmation, when all info are available from the beginning (see @subpage use_case_review_light)
- @ref nbgl_useCaseReviewStreamingStart() to draw the pages of a regular coin transaction review, when all info are not available from the beginning (see @subpage use_case_review_streaming)
- for address verification:
- @ref nbgl_useCaseAddressConfirmation() to draw an address confirmation page, with a possibility to see it as QR Code (see @subpage use_case_addr_confirm)
- @ref nbgl_useCaseAddressConfirmationExt() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs (see @subpage use_case_addr_confirm_ext)
- @ref nbgl_useCaseAddressReview() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs (see @subpage use_case_addr_review)
- for keypad:
- @ref nbgl_useCaseKeypadPIN() to draw a default keypad implementation with hidden digits (see @subpage use_case_keypad)
- @ref nbgl_useCaseKeypadDigits() to draw a default keypad implementation, showing digits (see @subpage use_case_keypad)
Expand All @@ -69,6 +68,9 @@ Some APIs have also been kept for backward compatibility, and for some rare case
- @ref nbgl_useCaseReviewStart() to draw the cover page of a review (initial page, without data)
- @ref nbgl_useCaseStaticReview() to draw the data pages of a regular review, when all info are available from the beginning (all pages but the cover one)
- @ref nbgl_useCaseRegularReview() to draw the data pages of a regular review (all pages but the cover one)
- for address verification:
- @ref nbgl_useCaseAddressConfirmation() to draw an address confirmation page, with a possibility to see it as QR Code
- @ref nbgl_useCaseAddressConfirmationExt() to draw an address confirmation page, with a possibility to see it as QR Code and some extra tag/value pairs
- for rare reviews:
- @ref nbgl_useCaseForwardOnlyReview() to draw the pages of a forward-only review (without back key)
- @ref nbgl_useCaseViewDetails() to draw the pages displaying the full value of a given long data of a review
Expand Down Expand Up @@ -155,6 +157,22 @@ void appMain(void) {
}
@endcode

@subsubsection use_case_home_settings_with_action Home & Settings screen with action button Use Case

\image{inline} html UseCase-HomeAction.png "caption" height=300

For some rare applications, one may need an action button in the Home screen, to perform either:

- The main action of the Application
- Or an side-action, as to display an address

The \b action argument of @ref nbgl_useCaseHomeAndSettings() can be used for that. This structure (@ref nbgl_homeAction_t)
enables to specify:

- A text & an icon for the button
- A function to be called when the button is touched
- The type of button (either @ref STRONG_HOME_ACTION for main action, in black, or @ref SOFT_HOME_ACTION for side action, in white)

@subsection use_case_confirm Confirmation Use Case

\image{inline} html UseCase-Confirm.png "caption" height=300
Expand Down Expand Up @@ -447,54 +465,16 @@ void staticReview(void) {
}
@endcode

@subsection use_case_addr_confirm Address Confirmation Use Case

\image{inline} html UseCase-AddressConfirmation.png "caption" height=300

When an address needs to be confirmed, it can be displayed in a Address Confirmation Use Case, at first as simple page with
the raw address (as text) and a black button/Footer pair to choose to confirm or reject the address.

An extra button under the raw address enables to open a modal page to see the address as a QR code.

The @ref nbgl_useCaseAddressConfirmation() function enables to create such a page, with the following parameters:

- the address to confirm (NULL terminated string)
- a callback called when button or footer is touched (if true, confirm, if false reject)
@subsection use_case_addr_review Address Review Use Case

Here is the code to display something similar to example picture:
\image{inline} html UseCase-AddressReview.png "caption" height=500

@code
// called when either confirm button or reject token is called
static void displayAddressCallback(bool confirm) {
if (confirm) {
nbgl_useCaseStatus("Address\nVerified",true,app_fullEthereum);
}
else {
nbgl_useCaseStatus("Address rejected",false,app_fullEthereum);
}
}

// called when tapping on review start page to actually display address
static void displayAddr(void) {
nbgl_useCaseAddressConfirmation("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", &displayAddressCallback);
}

void app_ethereumVerifyAddress(void) {
nbgl_useCaseReviewStart(myAppIcon,"Verify MyCoin\naddress",NULL,"Cancel",
displayAddr, appMain);
}
@endcode

@subsection use_case_addr_confirm_ext Extended Address Confirmation Use Case

\image{inline} html UseCase-AddressConfirmationExt.png "caption" height=500

When an address needs to be confirmed, it can be displayed in a Address Confirmation Use Case, at first as simple page with
the raw address (as text). An extra button under the raw address enables to open a modal page to see the address as a QR code.
When an address needs to be confirmed, it can be displayed in a Address Review Use Case.
After a title page, a second page is displayed with the raw address (as text). An extra button under the raw address enables to open a modal page to see the address as a QR code.
Moreover, if extra information need to be displayed, for example a derivation path, it is provided in a second page, also containing
a black button/Footer pair to choose to confirm or reject the address.

The @ref nbgl_useCaseAddressConfirmationExt() function enables to create such a set of pages, with the following parameters:
The @ref nbgl_useCaseAddressReview() function enables to create such a set of pages, with the following parameters:

- the address to confirm (NULL terminated string)
- a callback called when button or footer is touched (if true, confirm, if false reject)
Expand Down Expand Up @@ -522,14 +502,13 @@ static void displayAddressCallback(bool confirm) {
}
}

// called when tapping on review start page to actually display address
static void displayAddr(void) {
nbgl_useCaseAddressConfirmationExt("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg", &displayAddressCallback, &pairList);
}

void app_ethereumVerifyAddress(void) {
nbgl_useCaseReviewStart(myAppIcon,"Verify MyCoin\naddress",NULL,"Cancel",
displayAddr, appMain);
nbgl_useCaseAddressReview("bc1pkdcufjh6dxjaaa05hudvxqg5fhspfmwmp8g92gq8cv4gwwnmgrfqfd4jlg",
&pairList
myAppIcon,
"Verify MyCoin\naddress",
NULL,"Cancel",
appMain);
}
@endcode

Expand Down
Binary file not shown.
Binary file not shown.
Binary file added lib_nbgl/doc/resources/UseCase-AddressReview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed lib_nbgl/doc/resources/UseCase-Home.png
Binary file not shown.
Binary file added lib_nbgl/doc/resources/UseCase-HomeAction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 11 additions & 8 deletions lib_nbgl/include/nbgl_layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,21 +277,24 @@ typedef struct {
*
*/
typedef enum {
ROUNDED_AND_FOOTER_STYLE
= 0, ///< A rounded black background full width button on top of a footer
BOTH_ROUNDED_STYLE ///< A rounded black background full width button on top of a rounded white
///< background full width button
ROUNDED_AND_FOOTER_STYLE = 0, ///< A black background button on top of a footer
STRONG_ACTION_AND_FOOTER_STYLE, ///< A black button on top of a footer, with a separation line
SOFT_ACTION_AND_FOOTER_STYLE ///< A white button on top of a footer, with a separation line
} nbgl_layoutChoiceButtonsStyle_t;

// for backward compatibility
#define BOTH_ROUNDED_STYLE SOFT_ACTION_AND_FOOTER_STYLE

/**
* @brief This structure contains info to build a pair of buttons, one on top of the other.
*
* @note the pair of button is automatically put on bottom of screen, in the footer
*/
typedef struct {
const char *topText; ///< up-button text (index 0)
const char *bottomText; ///< bottom-button text (index 1)
uint8_t token; ///< the token that will be used as argument of the callback
const char *topText; ///< up-button text (index 0)
const char *bottomText; ///< bottom-button text (index 1)
const nbgl_icon_details_t *topIcon; ///< icon of top button
uint8_t token; ///< the token that will be used as argument of the callback
nbgl_layoutChoiceButtonsStyle_t style; ///< the style of the pair
#ifdef HAVE_PIEZO_SOUND
tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played
Expand Down Expand Up @@ -501,7 +504,7 @@ typedef struct {
} textAndNav; ///< if type is @ref FOOTER_TEXT_AND_NAV
nbgl_layoutNavigationBar_t navigation; ///< if type is @ref FOOTER_NAV
nbgl_layoutButton_t button; ///< if type is @ref FOOTER_SIMPLE_BUTTON
nbgl_layoutChoiceButtons_t choiceButtons; ///< if type is @ref FOOTER_SIMPLE_BUTTON
nbgl_layoutChoiceButtons_t choiceButtons; ///< if type is @ref FOOTER_CHOICE_BUTTONS
};
} nbgl_layoutFooter_t;

Expand Down
4 changes: 3 additions & 1 deletion lib_nbgl/include/nbgl_page.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ typedef struct nbgl_pageInfoDescription_s {
uint8_t tapActionToken; ///< the token that will be used as argument of the onActionCallback,
///< when tapped or swiped
const char
*actionButtonText; ///< if not NULL a black "action" button is set under the centered info
*actionButtonText; ///< if not NULL an "action" button is set under the centered info
const nbgl_icon_details_t *actionButtonIcon; ///< potential icon of "action" button
nbgl_layoutButtonStyle_t actionButtonStyle; ///< style of "action" button
tune_index_e
tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when button/footer is pressed
} nbgl_pageInfoDescription_t;
Expand Down
16 changes: 15 additions & 1 deletion lib_nbgl/include/nbgl_use_case.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,24 @@ typedef struct {
uint8_t nbContents; ///< number of contents
} nbgl_genericContents_t;

/**
* @brief The different types of action button in Home Screen
*
*/
typedef enum {
STRONG_HOME_ACTION = 0, ///< Black button, implicating the main action of the App
SOFT_HOME_ACTION ///< White button, more for extended features
} nbgl_homeActionStyle_t;

/**
* @brief Structure describing the action button in Home Screen
*
*/
typedef struct {
const char *text; ///< text to use in action button in Home page
const nbgl_icon_details_t *icon; ///< icon to use in action button in Home page
nbgl_callback_t callback; ///< function to call when action button is touched in Home page
nbgl_callback_t callback; ///< function to call when action button is touched in Home page
nbgl_homeActionStyle_t style; ///< style of action button
} nbgl_homeAction_t;

/**
Expand Down
87 changes: 40 additions & 47 deletions lib_nbgl/src/nbgl_layout.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,23 @@
#define TAG_VALUE_ICON_WIDTH 32

#ifdef TARGET_STAX
#define RADIO_CHOICE_HEIGHT 96
#define FOOTER_HEIGHT 80
#define BAR_INTERVALE 12
#define BACK_KEY_WIDTH 88
#define FOOTER_BUTTON_HEIGHT 128
#define UP_FOOTER_BUTTON_HEIGHT 120
#define RADIO_CHOICE_HEIGHT 96
#define FOOTER_HEIGHT 80
#define BAR_INTERVALE 12
#define BACK_KEY_WIDTH 88
#define FOOTER_BUTTON_HEIGHT 128
#define UP_FOOTER_BUTTON_HEIGHT 120
#define ROUNDED_AND_FOOTER_FOOTER_HEIGHT 192
#define ACTION_AND_FOOTER_FOOTER_HEIGHT 216
#else // TARGET_STAX
#define RADIO_CHOICE_HEIGHT 92
#define FOOTER_HEIGHT 80
#define BAR_INTERVALE 16
#define BACK_KEY_WIDTH 104
#define FOOTER_BUTTON_HEIGHT 136
#define UP_FOOTER_BUTTON_HEIGHT 136
#define RADIO_CHOICE_HEIGHT 92
#define FOOTER_HEIGHT 80
#define BAR_INTERVALE 16
#define BACK_KEY_WIDTH 104
#define FOOTER_BUTTON_HEIGHT 136
#define UP_FOOTER_BUTTON_HEIGHT 136
#define ROUNDED_AND_FOOTER_FOOTER_HEIGHT 208
#define ACTION_AND_FOOTER_FOOTER_HEIGHT 232
#endif // TARGET_STAX

// refresh period of the spinner, in ms
Expand Down Expand Up @@ -1765,6 +1769,7 @@ int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceBu
footerDesc.choiceButtons.bottomText = info->bottomText;
footerDesc.choiceButtons.token = info->token;
footerDesc.choiceButtons.topText = info->topText;
footerDesc.choiceButtons.topIcon = info->topIcon;
footerDesc.choiceButtons.style = info->style;
footerDesc.choiceButtons.tuneId = info->tuneId;
return nbgl_layoutAddExtendedFooter(layout, &footerDesc);
Expand Down Expand Up @@ -2749,7 +2754,7 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_
return -1;
}

// create bottomButton (in white) at first
// create bottom button (footer) at first
button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer);
obj = layoutAddCallbackObj(layoutInt,
(nbgl_obj_t *) button,
Expand All @@ -2761,31 +2766,25 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_
// associate with with index 1
obj->index = 1;
// put at the bottom of the container
button->obj.alignment = BOTTOM_MIDDLE;
if (footerDesc->choiceButtons.style == ROUNDED_AND_FOOTER_STYLE) {
button->obj.alignmentMarginY = 4; // 4 pixels from screen bottom
button->borderColor = WHITE;
}
else if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) {
button->obj.alignmentMarginY = 4; // 4 pixels from screen bottom
button->borderColor = WHITE; // not a real round button on Flex
}
button->innerColor = WHITE;
button->foregroundColor = BLACK;
button->obj.area.width = AVAILABLE_WIDTH;
button->obj.area.height = BUTTON_DIAMETER;
button->radius = BUTTON_RADIUS;
button->text = PIC(footerDesc->choiceButtons.bottomText);
button->fontId = SMALL_BOLD_FONT;
button->obj.touchMask = (1 << TOUCHED);
button->obj.touchId = CHOICE_2_ID;
button->obj.alignment = BOTTOM_MIDDLE;
button->obj.alignmentMarginY = 4; // 4 pixels from screen bottom
button->borderColor = WHITE;
button->innerColor = WHITE;
button->foregroundColor = BLACK;
button->obj.area.width = AVAILABLE_WIDTH;
button->obj.area.height = BUTTON_DIAMETER;
button->radius = BUTTON_RADIUS;
button->text = PIC(footerDesc->choiceButtons.bottomText);
button->fontId = SMALL_BOLD_FONT;
button->obj.touchMask = (1 << TOUCHED);
button->obj.touchId = CHOICE_2_ID;
// add to bottom container
layoutInt->footerContainer->children[layoutInt->footerContainer->nbChildren]
= (nbgl_obj_t *) button;
layoutInt->footerContainer->nbChildren++;

// add line if needed
if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) {
if (footerDesc->choiceButtons.style != ROUNDED_AND_FOOTER_STYLE) {
line = createHorizontalLine(layoutInt->layer);
line->obj.alignment = TOP_MIDDLE;
line->obj.alignmentMarginY = 4;
Expand All @@ -2795,7 +2794,7 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_
layoutInt->footerContainer->nbChildren++;
}

// then black button, on top of it
// then top button, on top of it
button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer);
obj = layoutAddCallbackObj(layoutInt,
(nbgl_obj_t *) button,
Expand All @@ -2807,8 +2806,8 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_
// associate with with index 0
obj->index = 0;
button->obj.alignment = TOP_MIDDLE;
button->obj.alignmentMarginY = 24; // 12 pixels from bottom button
if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) {
button->obj.alignmentMarginY = BOTTOM_BORDER_MARGIN; // 24 pixels from top of container
if (footerDesc->choiceButtons.style == SOFT_ACTION_AND_FOOTER_STYLE) {
button->innerColor = WHITE;
button->borderColor = LIGHT_GRAY;
button->foregroundColor = BLACK;
Expand All @@ -2822,6 +2821,9 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_
button->obj.area.height = BUTTON_DIAMETER;
button->radius = BUTTON_RADIUS;
button->text = PIC(footerDesc->choiceButtons.topText);
button->icon = (footerDesc->choiceButtons.style != ROUNDED_AND_FOOTER_STYLE)
? PIC(footerDesc->choiceButtons.topIcon)
: NULL;
button->fontId = SMALL_BOLD_FONT;
button->obj.touchMask = (1 << TOUCHED);
button->obj.touchId = CHOICE_1_ID;
Expand All @@ -2830,21 +2832,12 @@ int nbgl_layoutAddExtendedFooter(nbgl_layout_t *layout, const nbgl_layoutFooter_
= (nbgl_obj_t *) button;
layoutInt->footerContainer->nbChildren++;

#ifdef TARGET_STAX
if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) {
layoutInt->footerContainer->obj.area.height = 232;
if (footerDesc->choiceButtons.style != ROUNDED_AND_FOOTER_STYLE) {
layoutInt->footerContainer->obj.area.height = ACTION_AND_FOOTER_FOOTER_HEIGHT;
}
else {
layoutInt->footerContainer->obj.area.height = 192;
layoutInt->footerContainer->obj.area.height = ROUNDED_AND_FOOTER_FOOTER_HEIGHT;
}
#else // TARGET_STAX
if (footerDesc->choiceButtons.style == BOTH_ROUNDED_STYLE) {
layoutInt->footerContainer->obj.area.height = 232;
}
else {
layoutInt->footerContainer->obj.area.height = 208;
}
#endif // TARGET_STAX

break;
}
Expand Down
Loading

0 comments on commit 766b9c1

Please sign in to comment.