diff --git a/lib_nbgl/include/nbgl_content.h b/lib_nbgl/include/nbgl_content.h index 60929909f..c7b5a1c36 100644 --- a/lib_nbgl/include/nbgl_content.h +++ b/lib_nbgl/include/nbgl_content.h @@ -241,6 +241,19 @@ typedef struct { #endif // HAVE_PIEZO_SOUND } nbgl_contentBarsList_t; +/** + * @brief This structure contains data to build a @ref BARS_LIST_ICONS content + */ +typedef struct nbgl_contentBarsListIcons_s { + const char *const *barTexts; ///< array of texts for each bar (nbBars items, in black/bold) + const nbgl_icon_details_t *const *barIcons; ///< a buffer containing the 1BPP icons + const uint8_t *tokens; ///< array of tokens, one for each bar (nbBars items) + uint8_t nbBars; ///< number of elements in barTexts and tokens array +#ifdef HAVE_PIEZO_SOUND + tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played when a bar is touched +#endif // HAVE_PIEZO_SOUND +} nbgl_contentBarsListIcons_t; + /** * @brief The different types of predefined contents * @@ -255,11 +268,15 @@ typedef enum { SWITCHES_LIST, ///< list of switches with descriptions INFOS_LIST, ///< list of infos with titles CHOICES_LIST, ///< list of choices through radio buttons - BARS_LIST ///< list of touchable bars (with > on the right to go to sub-pages) + BARS_LIST, ///< list of touchable bars (with > on the right to go to sub-pages) + BARS_LIST_ICONS ///< list of touchable bars (with > on the right and icon on the left) } nbgl_contentType_t; /** * @brief Union of the different type of contents + * + * @note This union is duplicated in nbgl_page.h: @ref nbgl_pageContent_t + * */ typedef union { nbgl_contentCenteredInfo_t centeredInfo; ///< @ref CENTERED_INFO type @@ -272,6 +289,7 @@ typedef union { nbgl_contentInfoList_t infosList; ///< @ref INFOS_LIST type nbgl_contentRadioChoice_t choicesList; ///< @ref CHOICES_LIST type nbgl_contentBarsList_t barsList; ///< @ref BARS_LIST type + nbgl_contentBarsListIcons_t barsListIcons; ///< @ref BARS_LIST_ICONS type } nbgl_content_u; /** diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index 3b1971de8..2e919a480 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -319,7 +319,13 @@ int nbgl_layoutAddSwitch(nbgl_layout_t *layout, const nbgl_layoutSwitch_t *switc int nbgl_layoutAddText(nbgl_layout_t *layout, const char *text, const char *subText); int nbgl_layoutAddRadioChoice(nbgl_layout_t *layout, const nbgl_layoutRadioChoice_t *choices); int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info); +int nbgl_layoutAddQRCodeIcon(nbgl_layout_t *layout, + const nbgl_layoutQRCode_t *info, + const nbgl_icon_details_t *icon); int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceButtons_t *info); +int nbgl_layoutAddChoiceButtonsIcon(nbgl_layout_t *layout, + const nbgl_layoutChoiceButtons_t *info, + const nbgl_icon_details_t *icon); int nbgl_layoutAddTagValueList(nbgl_layout_t *layout, const nbgl_layoutTagValueList_t *list); int nbgl_layoutAddLargeCaseText(nbgl_layout_t *layout, const char *text); int nbgl_layoutAddSeparationLine(nbgl_layout_t *layout); diff --git a/lib_nbgl/include/nbgl_page.h b/lib_nbgl/include/nbgl_page.h index 9915f7a0a..cb63d9310 100644 --- a/lib_nbgl/include/nbgl_page.h +++ b/lib_nbgl/include/nbgl_page.h @@ -48,6 +48,9 @@ typedef nbgl_contentInfoLongPress_t nbgl_pageInfoLongPress_t; /** * @brief This structure contains data to build a page in multi-pages mode (@ref * nbgl_pageDrawGenericContent) + * + * @note This union is duplicated in nbgl_content.h: @ref nbgl_content_u + * */ typedef struct nbgl_pageContent_s { const char *title; ///< text for the title of the page (if NULL, no title) @@ -67,6 +70,7 @@ typedef struct nbgl_pageContent_s { nbgl_contentInfoList_t infosList; ///< @ref INFOS_LIST type nbgl_contentRadioChoice_t choicesList; ///< @ref CHOICES_LIST type nbgl_contentBarsList_t barsList; ///< @ref BARS_LIST type + nbgl_contentBarsListIcons_t barsListIcons; ///< @ref BARS_LIST_ICONS type }; } nbgl_pageContent_t; @@ -203,6 +207,10 @@ nbgl_page_t *nbgl_pageDrawSpinner(nbgl_layoutTouchCallback_t onActionCallback, c nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_screenTickerConfiguration_t *ticker, const nbgl_pageInfoDescription_t *info); +nbgl_page_t *nbgl_pageDrawInfoIcon(nbgl_layoutTouchCallback_t onActionCallback, + const nbgl_screenTickerConfiguration_t *ticker, + const nbgl_pageInfoDescription_t *info, + const nbgl_icon_details_t *topIcon); nbgl_page_t *nbgl_pageDrawConfirmation(nbgl_layoutTouchCallback_t onActionCallback, const nbgl_pageConfirmationDescription_t *info); nbgl_page_t *nbgl_pageDrawGenericContentExt(nbgl_layoutTouchCallback_t onActionCallback, diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index e22d1c03e..d5a0a2296 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -142,6 +142,15 @@ void nbgl_useCaseHomeExt(const char *appName, nbgl_callback_t actionCallback, nbgl_callback_t topRightCallback, nbgl_callback_t quitCallback); +void nbgl_useCaseHomeExtIcon(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + bool withSettings, + const char *actionButtonText, + nbgl_callback_t actionCallback, + nbgl_callback_t topRightCallback, + nbgl_callback_t quitCallback, + const nbgl_icon_details_t *icon); void nbgl_useCasePlugInHome(const char *plugInName, const char *appName, const nbgl_icon_details_t *appIcon, diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index fc0bb4732..bdcef97dc 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -1032,8 +1032,12 @@ int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *ba } if (barLayout->subText != NULL) { textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); - - textArea->textColor = BLACK; + if (barLayout->iconLeft != NULL) { + textArea->textColor = DARK_GRAY; + } + else { + textArea->textColor = BLACK; + } textArea->text = PIC(barLayout->subText); textArea->textAlignment = MID_LEFT; textArea->fontId = SMALL_REGULAR_FONT; @@ -1044,6 +1048,18 @@ int nbgl_layoutAddTouchableBar(nbgl_layout_t *layout, const nbgl_layoutBar_t *ba textArea->obj.area.width = container->obj.area.width; textArea->obj.area.height = nbgl_getTextHeightInWidth( textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); + if ((barLayout->iconLeft != NULL) && (barLayout->centered != true)) { + textArea->obj.alignmentMarginX = 12; + } + if (barLayout->iconLeft != NULL) { + textArea->obj.alignmentMarginY += 12; + textArea->obj.area.width -= imageLeft->buffer->width + 12; + textArea->obj.alignTo = (nbgl_obj_t *) imageLeft; + textArea->obj.alignment = MID_RIGHT; + } + if (barLayout->iconRight != NULL) { + textArea->obj.area.width -= ((nbgl_icon_details_t *) PIC(barLayout->iconRight))->width; + } container->children[container->nbChildren] = (nbgl_obj_t *) textArea; container->nbChildren++; container->obj.area.height += textArea->obj.area.height + 16; @@ -1834,13 +1850,17 @@ int nbgl_layoutAddCenteredInfo(nbgl_layout_t *layout, const nbgl_layoutCenteredI * * @param layout the current layout * @param info structure giving the description of buttons (texts, icons, layout) + * @param icon icon blow QR code, above line(s) * @return >= 0 if OK */ -int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) +int nbgl_layoutAddQRCodeIcon(nbgl_layout_t *layout, + const nbgl_layoutQRCode_t *info, + const nbgl_icon_details_t *icon) { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; nbgl_container_t *container; nbgl_text_area_t *textArea = NULL; + nbgl_image_t *image = NULL; nbgl_qrcode_t *qrcode; uint16_t fullHeight = 0; @@ -1851,13 +1871,14 @@ int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); - // get container children (max 2 (QRCode + text1/text2)) - container->children = nbgl_containerPoolGet(2, layoutInt->layer); + // get container children (max 4: QRCode + icon + text1 + text2) + container->children = nbgl_containerPoolGet(4, layoutInt->layer); container->nbChildren = 0; qrcode = (nbgl_qrcode_t *) nbgl_objPoolGet(QR_CODE, layoutInt->layer); // version is forced to V10 if url is longer than 62 characters - if (strlen(PIC(info->url)) > 62) { + if ((strlen(PIC(info->url)) > 62) + || ((icon != NULL) && (info->text1 != NULL) && (info->text2 != NULL))) { qrcode->version = QRCODE_V10; } else { @@ -1878,6 +1899,19 @@ int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) container->children[container->nbChildren] = (nbgl_obj_t *) qrcode; container->nbChildren++; + if (icon != NULL) { + image = (nbgl_image_t *) nbgl_objPoolGet(IMAGE, layoutInt->layer); + image->foregroundColor = BLACK; + image->buffer = PIC(icon); + image->obj.area.bpp = NBGL_BPP_1; + image->obj.alignment = BOTTOM_MIDDLE; + image->obj.alignTo = (nbgl_obj_t *) container->children[container->nbChildren - 1]; + image->obj.alignmentMarginY = 16; + + fullHeight += image->buffer->height; + container->children[container->nbChildren] = (nbgl_obj_t *) image; + container->nbChildren++; + } if (info->text1 != NULL) { textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); textArea->textColor = BLACK; @@ -1890,14 +1924,18 @@ int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); textArea->obj.alignment = BOTTOM_MIDDLE; textArea->obj.alignTo = (nbgl_obj_t *) container->children[container->nbChildren - 1]; - textArea->obj.alignmentMarginY = 40; - + if (icon == NULL) { + textArea->obj.alignmentMarginY = 40; + } + else { + textArea->obj.alignmentMarginY = 12; + } fullHeight += textArea->obj.area.height; container->children[container->nbChildren] = (nbgl_obj_t *) textArea; container->nbChildren++; } - else if (info->text2 != NULL) { + if (info->text2 != NULL) { textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); textArea->textColor = DARK_GRAY; textArea->text = PIC(info->text2); @@ -1909,7 +1947,12 @@ int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); textArea->obj.alignment = BOTTOM_MIDDLE; textArea->obj.alignTo = (nbgl_obj_t *) container->children[container->nbChildren - 1]; - textArea->obj.alignmentMarginY = 40; + if (icon == NULL) { + textArea->obj.alignmentMarginY = 40; + } + else { + textArea->obj.alignmentMarginY = 12; + } fullHeight += textArea->obj.area.height; @@ -1936,6 +1979,18 @@ int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) return 0; } +/** + * @brief Creates an area on the center of the main panel, with a QRCode, + * a possible text in black (bold) under it, and a possible text in black under it + * + * @param layout the current layout + * @param info structure giving the description of buttons (texts, icons, layout) + * @return >= 0 if OK + */ +int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) +{ + return nbgl_layoutAddQRCodeIcon(layout, info, NULL); +} #endif // NBGL_QRCODE #ifdef HAVE_SE_TOUCH @@ -1944,9 +1999,12 @@ int nbgl_layoutAddQRCode(nbgl_layout_t *layout, const nbgl_layoutQRCode_t *info) * * @param layout the current layout * @param info structure giving the description of buttons (texts, icons, layout) + * @param icon a buffer containing the 1BPP icon for top button * @return >= 0 if OK */ -int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceButtons_t *info) +int nbgl_layoutAddChoiceButtonsIcon(nbgl_layout_t *layout, + const nbgl_layoutChoiceButtons_t *info, + const nbgl_icon_details_t *icon) { layoutObj_t *obj; nbgl_button_t *topButton, *bottomButton; @@ -2007,6 +2065,7 @@ int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceBu else { topButton->obj.alignmentMarginY = 4; // 4 pixels from bottom button } + topButton->icon = icon; topButton->innerColor = BLACK; topButton->borderColor = BLACK; topButton->foregroundColor = WHITE; @@ -2023,6 +2082,18 @@ int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceBu return 0; } +/** + * @brief Creates two buttons to make a choice. Both buttons are mandatory + * + * @param layout the current layout + * @param info structure giving the description of buttons (texts, icons, layout) + * @return >= 0 if OK + */ +int nbgl_layoutAddChoiceButtons(nbgl_layout_t *layout, const nbgl_layoutChoiceButtons_t *info) +{ + return nbgl_layoutAddChoiceButtonsIcon(layout, info, NULL); +} + /** * @brief Creates a list of [tag,value] pairs * diff --git a/lib_nbgl/src/nbgl_page.c b/lib_nbgl/src/nbgl_page.c index 501819343..3f758acd6 100644 --- a/lib_nbgl/src/nbgl_page.c +++ b/lib_nbgl/src/nbgl_page.c @@ -176,6 +176,22 @@ static void addContent(nbgl_pageContent_t *content, nbgl_layout_t *layout) } break; } + case BARS_LIST_ICONS: { + uint8_t i; + for (i = 0; i < content->barsListIcons.nbBars; i++) { + nbgl_layoutBar_t bar; + bar.text = content->barsListIcons.barTexts[i]; + bar.subText = NULL; + bar.iconRight = &C_Next32px; + bar.iconLeft = content->barsListIcons.barIcons[i]; + bar.token = content->barsListIcons.tokens[i]; + bar.centered = false; + bar.tuneId = content->barsListIcons.tuneId; + nbgl_layoutAddTouchableBar(layout, &bar); + nbgl_layoutAddSeparationLine(layout); + } + break; + } } } @@ -262,11 +278,13 @@ nbgl_page_t *nbgl_pageDrawSpinner(nbgl_layoutTouchCallback_t onActionCallback, c * @param onActionCallback common callback for all actions on this page * @param ticker ticker configuration, set to NULL to disable it * @param info structure describing the centered info and other controls of this page + * @param topIcon a buffer containing the 1BPP icon for top button * @return the page context (or NULL if error) */ -nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, - const nbgl_screenTickerConfiguration_t *ticker, - const nbgl_pageInfoDescription_t *info) +nbgl_page_t *nbgl_pageDrawInfoIcon(nbgl_layoutTouchCallback_t onActionCallback, + const nbgl_screenTickerConfiguration_t *ticker, + const nbgl_pageInfoDescription_t *info, + const nbgl_icon_details_t *topIcon) { nbgl_layoutDescription_t layoutDescription; nbgl_layout_t *layout; @@ -330,7 +348,7 @@ nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionC .token = info->bottomButtonsToken, .style = BOTH_ROUNDED_STYLE, .tuneId = info->tuneId}; - nbgl_layoutAddChoiceButtons(layout, &buttonsInfo); + nbgl_layoutAddChoiceButtonsIcon(layout, &buttonsInfo, topIcon); } else { nbgl_layoutButton_t buttonInfo = {.fittingContent = false, @@ -365,6 +383,22 @@ nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionC return (nbgl_page_t *) layout; } +/** + * @brief draw a page with a centered info (icon and/or texts) with a touchable footer, + * in a potential "tapable" area, with an optional top-right button, with an optional bottom button + * + * @param onActionCallback common callback for all actions on this page + * @param ticker ticker configuration, set to NULL to disable it + * @param info structure describing the centered info and other controls of this page + * @return the page context (or NULL if error) + */ +nbgl_page_t *nbgl_pageDrawInfo(nbgl_layoutTouchCallback_t onActionCallback, + const nbgl_screenTickerConfiguration_t *ticker, + const nbgl_pageInfoDescription_t *info) +{ + return (nbgl_page_t *) nbgl_pageDrawInfoIcon(onActionCallback, ticker, info, NULL); +} + /** * @brief draw a confirmation page, with a centered info (icon and/or text), a button to confirm and * a footer to cancel diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index 419a571b1..c4bd65eb7 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -940,15 +940,17 @@ void nbgl_useCaseHome(const char *appName, * NULL) * @param topRightCallback callback called when top-right button is touched * @param quitCallback callback called when quit button is touched + * @param icon a buffer containing the 1BPP icon for top button */ -void nbgl_useCaseHomeExt(const char *appName, - const nbgl_icon_details_t *appIcon, - const char *tagline, - bool withSettings, - const char *actionButtonText, - nbgl_callback_t actionCallback, - nbgl_callback_t topRightCallback, - nbgl_callback_t quitCallback) +void nbgl_useCaseHomeExtIcon(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + bool withSettings, + const char *actionButtonText, + nbgl_callback_t actionCallback, + nbgl_callback_t topRightCallback, + nbgl_callback_t quitCallback, + const nbgl_icon_details_t *icon) { reset_callbacks(); @@ -1011,10 +1013,48 @@ void nbgl_useCaseHomeExt(const char *appName, if (actionButtonText != NULL) { info.centeredInfo.offsetY -= 40; } - pageContext = nbgl_pageDrawInfo(&pageCallback, NULL, &info); + pageContext = nbgl_pageDrawInfoIcon(&pageCallback, NULL, &info, icon); nbgl_refreshSpecial(FULL_COLOR_CLEAN_REFRESH); } +/** + * @brief draws the extended version of home page of an app (page on which we land when launching it + * from dashboard) + * @note it enables to use an action button (black) + * + * @param appName app name + * @param appIcon app icon + * @param tagline text under app name (if NULL, it will be "This app enables signing transactions on + * the network.") + * @param withSettings if true, use a "settings" (wheel) icon in bottom button, otherwise a "info" + * (i) + * @param actionButtonText if not NULL, text used for an action button (in black, on top of "Quit + * App" button) + * @param actionCallback callback called when action button is touched (if actionButtonText is not + * NULL) + * @param topRightCallback callback called when top-right button is touched + * @param quitCallback callback called when quit button is touched + */ +void nbgl_useCaseHomeExt(const char *appName, + const nbgl_icon_details_t *appIcon, + const char *tagline, + bool withSettings, + const char *actionButtonText, + nbgl_callback_t actionCallback, + nbgl_callback_t topRightCallback, + nbgl_callback_t quitCallback) +{ + nbgl_useCaseHomeExtIcon(appName, + appIcon, + tagline, + withSettings, + actionButtonText, + actionCallback, + topRightCallback, + quitCallback, + NULL); +} + /** * @brief draws the home page of a plug-in app (page on which we land when launching it from * dashboard)