diff --git a/Makefile.defines b/Makefile.defines index bd4d50810..8f5cbf2d7 100644 --- a/Makefile.defines +++ b/Makefile.defines @@ -174,6 +174,7 @@ DEFINES += HAVE_INAPP_BLE_PAIRING DEFINES += HAVE_NBGL DEFINES += HAVE_PIEZO_SOUND DEFINES += HAVE_SE_TOUCH +DEFINES += HAVE_SE_EINK_DISPLAY DEFINES += NBGL_PAGE DEFINES += NBGL_USE_CASE DEFINES += SCREEN_SIZE_WALLET @@ -189,7 +190,8 @@ DEFINES += HAVE_INAPP_BLE_PAIRING DEFINES += HAVE_NBGL DEFINES += HAVE_PIEZO_SOUND DEFINES += HAVE_SE_TOUCH -DEFINES += HAVE_HW_TOUCH_SWIPE +DEFINES += HAVE_SE_EINK_DISPLAY +# DEFINES += HAVE_HW_TOUCH_SWIPE DEFINES += NBGL_PAGE DEFINES += NBGL_USE_CASE DEFINES += SCREEN_SIZE_WALLET diff --git a/lib_cxng/include/lcx_crc.h b/lib_cxng/include/lcx_crc.h index 98fb545fb..b68d5a688 100644 --- a/lib_cxng/include/lcx_crc.h +++ b/lib_cxng/include/lcx_crc.h @@ -60,6 +60,35 @@ uint16_t cx_crc16(const void *buffer, size_t len); */ uint16_t cx_crc16_update(uint16_t crc, const void *buffer, size_t len); +/** CRC32 initial value */ +#define CX_CRC32_INIT 0xFFFFFFFF + +/** + * @brief Computes a 32-bit checksum value. + * + * @details The 32-bit value is computed according to the CRC32 CCITT definition. + * + * @param[in] buffer The buffer to compute the CRC over. + * + * @param[in] len Bytes length of the buffer. + * + * @return Current CRC value. + */ +uint32_t cx_crc32(const void *buf, size_t len); + +/** + * @brief Accumulates more data to CRC. + * + * @param[in] crc CRC value to be updated. + * + * @param[in] buffer The buffer to compute the CRC over. + * + * @param[in] len Bytes length of the buffer. + * + * @return Updated CRC value. + */ +uint32_t cx_crc32_update(uint32_t crc_state, const void *buf, size_t len); + #endif // HAVE_CRC #endif // LCX_CRC_H diff --git a/lib_cxng/src/cx_crc.h b/lib_cxng/src/cx_crc.h index a07f07579..d4b56b660 100644 --- a/lib_cxng/src/cx_crc.h +++ b/lib_cxng/src/cx_crc.h @@ -21,14 +21,7 @@ #ifdef HAVE_CRC -#include -#include - -/** CRC32 initial value */ -#define CX_CRC32_INIT 0xFFFFFFFF - -uint32_t cx_crc32(const void *buf, size_t len); -uint32_t cx_crc32_update(uint32_t crc_state, const void *buf, size_t len); +#include "lcx_crc.h" #endif // HAVE_CRC diff --git a/lib_cxng/src/cx_crc32.c b/lib_cxng/src/cx_crc32.c index e83d21eb7..bf1b2933a 100644 --- a/lib_cxng/src/cx_crc32.c +++ b/lib_cxng/src/cx_crc32.c @@ -18,7 +18,7 @@ #ifdef HAVE_CRC -#include "cx_crc.h" +#include "lcx_crc.h" #include "cx_ram.h" static uint32_t reverse_32_bits(uint32_t value) diff --git a/lib_nbgl/doc/nbgl_use_case.dox b/lib_nbgl/doc/nbgl_use_case.dox index 68390d79f..1a24e30f4 100644 --- a/lib_nbgl/doc/nbgl_use_case.dox +++ b/lib_nbgl/doc/nbgl_use_case.dox @@ -61,7 +61,8 @@ A few APIs are available to draw typical Use-Cases, such as: - @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) - for keypad: - - @ref nbgl_useCaseKeypad() to draw a default keypad implementation (see @subpage use_case_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) @subsection use_case_home Home screen Use Case @@ -872,22 +873,28 @@ The @ref nbgl_useCaseSpinner() function enables to create such a page, with the \image{inline} html UseCase-Keypad.png "caption" height=350 -When a pincode is requested, a default keypad can be displayed. As show on the image above, it consists of: +We have here 2 different variants, allowing to show or hide the entered digits. + +When a pincode is requested, a default keypad can be displayed, with hidden digits. +As shown on the image above, it consists of: - a navigation bar at the top - a title area, specifying the type of pin code or operation requested - a hidden Digits area (the max nb of supported digits is 12) - the keypad at the bottom -The @ref nbgl_useCaseKeypad() function enables to create such page, with the following parameters: +The @ref nbgl_useCaseKeypadPIN() function enables to create such page, with the following parameters: - the title -- min and max pin code length +- min and max pin code lengths - a token for the navigation callback (if not provided, no navigation bar will appear) - a boolean to request a shuffled keypad - a tune value - callbacks for navigation and pin validation +The other variant, where digits don't need to be hidden is @ref nbgl_useCaseKeypadDigits(); +it takes the same parameters. + @note The \em backspace and \em validate buttons will be shown or hidden automatically. Here is the code to display something similar to example picture: @@ -907,7 +914,7 @@ static void pinentry_cb(int token, uint8_t index) { void ui_menu_pinentry_display(unsigned int value) { // Draw the keypad - nbgl_useCaseKeypad("Enter User PIN", + nbgl_useCaseKeypadPIN("Enter User PIN", 6, 12, TOKEN_PIN_ENTRY_BACK, diff --git a/lib_nbgl/include/nbgl_layout.h b/lib_nbgl/include/nbgl_layout.h index 49645ba87..577c1056a 100644 --- a/lib_nbgl/include/nbgl_layout.h +++ b/lib_nbgl/include/nbgl_layout.h @@ -317,6 +317,59 @@ typedef struct { #endif // HAVE_PIEZO_SOUND } nbgl_layoutButton_t; +/** + * @brief The different types of keyboard contents + * + */ +typedef enum { + KEYBOARD_WITH_SUGGESTIONS, ///< text entry area + suggestion buttons + KEYBOARD_WITH_BUTTON, ///< text entry area + confirmation button + NB_KEYBOARD_CONTENT_TYPES +} nbgl_layoutKeyboardContentType_t; + +/** + * @brief This structure contains info to build suggestion buttons + */ +typedef struct { + const char **buttons; ///< array of 4 strings for buttons (last ones can be NULL) + int firstButtonToken; ///< first token used for buttons, provided in onActionCallback (the next + ///< 3 values will be used for other buttons) + uint8_t nbUsedButtons; ///< the number of actually used buttons +} nbgl_layoutSuggestionButtons_t; + +/** + * @brief This structure contains info to build a confirmation button + */ +typedef struct { + const char *text; ///< text of the button + int token; ///< token of the button + bool active; ///< if true, button is active, otherwise inactive (grayed-out) +} nbgl_layoutConfirmationButton_t; + +/** + * @brief This structure contains info to build a keyboard content (controls that are linked to + * keyboard) + */ +typedef struct { + nbgl_layoutKeyboardContentType_t type; ///< type of content + const char *title; ///< centered title explaining the screen + const char *text; ///< already entered text + bool numbered; ///< if set to true, the text is preceded on the left by 'number.' + uint8_t number; ///< if numbered is true, number used to build 'number.' text + bool grayedOut; ///< if true, the text is grayed out (but not the potential number) + int textToken; ///< the token that will be used as argument of the callback when text is + ///< touched + union { + nbgl_layoutSuggestionButtons_t + suggestionButtons; /// used if type is @ref KEYBOARD_WITH_SUGGESTIONS + nbgl_layoutConfirmationButton_t + confirmationButton; /// used if type is @ref KEYBOARD_WITH_BUTTON + }; +#ifdef HAVE_PIEZO_SOUND + tune_index_e tuneId; ///< if not @ref NBGL_NO_TUNE, a tune will be played +#endif // HAVE_PIEZO_SOUND +} nbgl_layoutKeyboardContent_t; + /** * @brief The different types of extended header * @@ -527,43 +580,46 @@ int nbgl_layoutAddMenuList(nbgl_layout_t *layout, nbgl_layoutMenuList_t *list); /* layout objects for page with keyboard */ int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInfo); #ifdef HAVE_SE_TOUCH -int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, - uint8_t index, - uint32_t keyMask, - bool updateCasing, - keyboardCase_t casing); -bool nbgl_layoutKeyboardNeedsRefresh(nbgl_layout_t *layout, uint8_t index); -int nbgl_layoutAddSuggestionButtons(nbgl_layout_t *layout, - uint8_t nbUsedButtons, - const char *buttonTexts[NB_MAX_SUGGESTION_BUTTONS], - int firstButtonToken, - tune_index_e tuneId); -int nbgl_layoutUpdateSuggestionButtons(nbgl_layout_t *layout, - uint8_t index, - uint8_t nbUsedButtons, - const char *buttonTexts[NB_MAX_SUGGESTION_BUTTONS]); -int nbgl_layoutAddEnteredText(nbgl_layout_t *layout, - bool numbered, - uint8_t number, - const char *text, - bool grayedOut, - int offsetY, - int token); -int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, - uint8_t index, - bool numbered, - uint8_t number, - const char *text, - bool grayedOut); -int nbgl_layoutAddConfirmationButton(nbgl_layout_t *layout, - bool active, - const char *text, - int token, - tune_index_e tuneId); -int nbgl_layoutUpdateConfirmationButton(nbgl_layout_t *layout, +int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, - bool active, - const char *text); + uint32_t keyMask, + bool updateCasing, + keyboardCase_t casing); +bool nbgl_layoutKeyboardNeedsRefresh(nbgl_layout_t *layout, uint8_t index); +DEPRECATED int nbgl_layoutAddSuggestionButtons(nbgl_layout_t *layout, + uint8_t nbUsedButtons, + const char *buttonTexts[NB_MAX_SUGGESTION_BUTTONS], + int firstButtonToken, + tune_index_e tuneId); +DEPRECATED int nbgl_layoutUpdateSuggestionButtons( + nbgl_layout_t *layout, + uint8_t index, + uint8_t nbUsedButtons, + const char *buttonTexts[NB_MAX_SUGGESTION_BUTTONS]); +DEPRECATED int nbgl_layoutAddEnteredText(nbgl_layout_t *layout, + bool numbered, + uint8_t number, + const char *text, + bool grayedOut, + int offsetY, + int token); +DEPRECATED int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, + uint8_t index, + bool numbered, + uint8_t number, + const char *text, + bool grayedOut); +DEPRECATED int nbgl_layoutAddConfirmationButton(nbgl_layout_t *layout, + bool active, + const char *text, + int token, + tune_index_e tuneId); +DEPRECATED int nbgl_layoutUpdateConfirmationButton(nbgl_layout_t *layout, + uint8_t index, + bool active, + const char *text); +int nbgl_layoutAddKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content); +int nbgl_layoutUpdateKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content); #else // HAVE_SE_TOUCH int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, uint8_t index, uint32_t keyMask); int nbgl_layoutAddEnteredText(nbgl_layout_t *layout, const char *text, bool lettersOnly); @@ -580,8 +636,19 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, bool enableValidate, bool enableBackspace, bool enableDigits); -int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits); -int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t nbActive); +DEPRECATED int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits); +DEPRECATED int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, + uint8_t index, + uint8_t nbActive); +int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, + const char *title, + bool hidden, + uint8_t nbDigits, + const char *text); +int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, + bool hidden, + uint8_t nbActiveDigits, + const char *text); #else // HAVE_SE_TOUCH /* layout objects for pages with keypad (nanos) */ diff --git a/lib_nbgl/include/nbgl_obj.h b/lib_nbgl/include/nbgl_obj.h index d542029c6..f119d6494 100644 --- a/lib_nbgl/include/nbgl_obj.h +++ b/lib_nbgl/include/nbgl_obj.h @@ -336,10 +336,11 @@ typedef struct PACKED__ nbgl_switch_s { * @note if withBorder, the stroke of the border is fixed (3 pixels) */ typedef struct PACKED__ nbgl_progress_bar_s { - nbgl_obj_t obj; // common part - bool withBorder; ///< if set to true, a border in black surround the whole object - uint8_t state; ///< state of the progress, in % (from 0 to 100). - uint8_t previousState; ///< previous state of the progress, in % (from 0 to 100). + nbgl_obj_t obj; // common part + bool withBorder; ///< if set to true, a border in black surround the whole object + uint8_t state; ///< state of the progress, in % (from 0 to 100). + uint8_t previousState; ///< previous state of the progress, in % (from 0 to 100). + uint16_t previousWidth; color_t foregroundColor; ///< color of the inner progress bar and border (if applicable) } nbgl_progress_bar_t; diff --git a/lib_nbgl/include/nbgl_use_case.h b/lib_nbgl/include/nbgl_use_case.h index 37ab49e40..52dcd3a9c 100644 --- a/lib_nbgl/include/nbgl_use_case.h +++ b/lib_nbgl/include/nbgl_use_case.h @@ -316,14 +316,44 @@ void nbgl_useCaseAddressConfirmationExt(const char *address nbgl_choiceCallback_t callback, const nbgl_layoutTagValueList_t *tagValueList); #ifdef NBGL_KEYPAD -void nbgl_useCaseKeypad(const char *title, - uint8_t minDigits, - uint8_t maxDigits, - uint8_t backToken, - bool shuffled, - tune_index_e tuneId, - nbgl_pinValidCallback_t validatePinCallback, - nbgl_layoutTouchCallback_t actionCallback); +void nbgl_useCaseKeypadDigits(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_layoutTouchCallback_t actionCallback); +void nbgl_useCaseKeypadPIN(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_layoutTouchCallback_t actionCallback); +/** + * @deprecated + * See #nbgl_useCaseKeypadPIN + */ +DEPRECATED static inline void nbgl_useCaseKeypad(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_layoutTouchCallback_t actionCallback) +{ + nbgl_useCaseKeypadPIN(title, + minDigits, + maxDigits, + backToken, + shuffled, + tuneId, + validatePinCallback, + actionCallback); +} #endif #else // HAVE_SE_TOUCH void nbgl_useCaseHome(const char *appName, diff --git a/lib_nbgl/src/nbgl_layout.c b/lib_nbgl/src/nbgl_layout.c index 58ec7c09e..8c7caf2a6 100644 --- a/lib_nbgl/src/nbgl_layout.c +++ b/lib_nbgl/src/nbgl_layout.c @@ -117,11 +117,11 @@ static uint8_t nbTouchableControls = 0; #ifdef HAVE_DISPLAY_FAST_MODE // Unit step in % of touchable progress bar -#define HOLD_TO_APPROVE_STEP_PERCENT (10) +#define HOLD_TO_APPROVE_STEP_PERCENT (7) // Duration in ms the user must hold the progress bar // to make it progress HOLD_TO_APPROVE_STEP_PERCENT %. // This duration must be higher than the screen refresh duration. -#define HOLD_TO_APPROVE_STEP_DURATION_MS (150) +#define HOLD_TO_APPROVE_STEP_DURATION_MS (100) #else #define HOLD_TO_APPROVE_STEP_PERCENT (25) #define HOLD_TO_APPROVE_STEP_DURATION_MS (400) @@ -211,7 +211,8 @@ static void touchCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) if (layout->swipeUsage == SWIPE_USAGE_CUSTOM) { layoutObj->index = eventType; } - else if (layout->swipeUsage == SWIPE_USAGE_NAVIGATION) { + else if ((layout->swipeUsage == SWIPE_USAGE_NAVIGATION) + && ((nbgl_obj_t *) layout->container == obj)) { nbgl_container_t *navContainer; if (layout->footerType == FOOTER_NAV) { navContainer = (nbgl_container_t *) layout->footerContainer; @@ -323,11 +324,12 @@ static void longTouchCallback(nbgl_obj_t *obj, if (new_state != progressBar->state) { progressBar->previousState = progressBar->state; progressBar->state = new_state; + nbgl_redrawObject((nbgl_obj_t *) progressBar, false, false); // Ensure progress bar is fully drawn // before calling the callback. nbgl_refreshSpecialWithPostRefresh(BLACK_AND_WHITE_FAST_REFRESH, - POST_REFRESH_FORCE_POWER_ON); + POST_REFRESH_FORCE_POWER_ON_WITH_PIPELINE); } if (trigger_callback) { @@ -338,7 +340,9 @@ static void longTouchCallback(nbgl_obj_t *obj, } } // case of releasing a long press button (or getting out of it) - else if ((eventType == TOUCH_RELEASED) || (eventType == OUT_OF_TOUCH)) { + else if ((eventType == TOUCH_RELEASED) || (eventType == OUT_OF_TOUCH) + || (eventType == SWIPED_LEFT) || (eventType == SWIPED_RIGHT)) { + nbgl_wait_pipeline(); progressBar->state = 0; nbgl_redrawObject((nbgl_obj_t *) progressBar, false, false); nbgl_refreshSpecialWithPostRefresh(BLACK_AND_WHITE_REFRESH, POST_REFRESH_FORCE_POWER_OFF); @@ -2027,7 +2031,8 @@ int nbgl_layoutAddLongPressButton(nbgl_layout_t *layout, = (nbgl_obj_t **) nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); container->obj.alignment = BOTTOM_MIDDLE; container->obj.touchId = LONG_PRESS_BUTTON_ID; - container->obj.touchMask = ((1 << TOUCHING) | (1 << TOUCH_RELEASED) | (1 << OUT_OF_TOUCH)); + container->obj.touchMask = ((1 << TOUCHING) | (1 << TOUCH_RELEASED) | (1 << OUT_OF_TOUCH) + | (1 << SWIPED_LEFT) | (1 << SWIPED_RIGHT)); button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); button->obj.alignmentMarginX = BORDER_MARGIN; diff --git a/lib_nbgl/src/nbgl_layout_keyboard.c b/lib_nbgl/src/nbgl_layout_keyboard.c index a2b46d122..00571f2c3 100644 --- a/lib_nbgl/src/nbgl_layout_keyboard.c +++ b/lib_nbgl/src/nbgl_layout_keyboard.c @@ -21,7 +21,6 @@ #include "glyphs.h" #include "os_pic.h" #include "os_helpers.h" -#include "lcx_rng.h" /********************* * DEFINES @@ -38,6 +37,9 @@ enum { }; #endif // TARGET_STAX +#define TEXT_ENTRY_NORMAL_HEIGHT 64 +#define TEXT_ENTRY_COMPACT_HEIGHT 56 + /********************** * MACROS **********************/ @@ -172,6 +174,274 @@ bool keyboardSwipeCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) } #endif // TARGET_STAX +static nbgl_container_t *addTextEntry(nbgl_layoutInternal_t *layoutInt, + const char *title, + const char *text, + bool numbered, + uint8_t number, + bool grayedOut, + int textToken, + bool compactMode) +{ + nbgl_container_t *container; + nbgl_text_area_t *textArea; + layoutObj_t *obj; + uint16_t textEntryHeight + = (compactMode ? TEXT_ENTRY_COMPACT_HEIGHT : TEXT_ENTRY_NORMAL_HEIGHT) - 8; + + // create a container, to store title, entered text and underline + container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + container->nbChildren = 4; + container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); + container->obj.area.width = AVAILABLE_WIDTH; + container->obj.alignment = CENTER; + + if (title != NULL) { + // create text area for title + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = BLACK; + textArea->text = title; + textArea->textAlignment = CENTER; + textArea->fontId = SMALL_REGULAR_FONT; + textArea->wrapping = true; + textArea->obj.alignment = TOP_MIDDLE; + textArea->obj.area.width = AVAILABLE_WIDTH; + textArea->obj.area.height = nbgl_getTextHeightInWidth( + textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); + container->children[0] = (nbgl_obj_t *) textArea; + container->obj.area.height = textArea->obj.area.height; + } + + if (numbered) { + // create Word num typed text + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = BLACK; + snprintf(numText, sizeof(numText), "%d.", number); + textArea->text = numText; + textArea->textAlignment = CENTER; + textArea->fontId = LARGE_MEDIUM_1BPP_FONT; +#ifdef TARGET_STAX + textArea->obj.area.width = 50; +#else // TARGET_STAX + textArea->obj.area.width = 66; +#endif // TARGET_STAX + if (title != NULL) { + textArea->obj.alignmentMarginY = 8; + textArea->obj.alignTo = container->children[0]; + textArea->obj.alignment = BOTTOM_LEFT; + } + else { + textArea->obj.alignment = TOP_LEFT; + } + textArea->obj.area.height = textEntryHeight; + // set this text area as child of the container + container->children[1] = (nbgl_obj_t *) textArea; + } + + // create text area for entered text + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = grayedOut ? LIGHT_GRAY : BLACK; + textArea->text = text; + textArea->textAlignment = MID_LEFT; + textArea->fontId = LARGE_MEDIUM_1BPP_FONT; + if (title != NULL) { + textArea->obj.alignmentMarginY = 8; + textArea->obj.alignTo = container->children[0]; + textArea->obj.alignment = BOTTOM_LEFT; + } + else { + textArea->obj.alignment = TOP_LEFT; + } + textArea->obj.area.width = AVAILABLE_WIDTH; + if (numbered) { +#ifdef TARGET_STAX + textArea->obj.alignmentMarginX = 50; +#else // TARGET_STAX + textArea->obj.alignmentMarginX = 66; +#endif // TARGET_STAX + textArea->obj.area.width -= textArea->obj.alignmentMarginX; + } + textArea->obj.area.height = textEntryHeight; + textArea->autoHideLongLine = true; + + obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) textArea, textToken, NBGL_NO_TUNE); + if (obj == NULL) { + return NULL; + } + textArea->obj.touchMask = (1 << TOUCHED); + textArea->obj.touchId = ENTERED_TEXT_ID; + container->children[2] = (nbgl_obj_t *) textArea; + container->obj.area.height + += textArea->obj.area.height + textArea->obj.alignmentMarginY + ((title != NULL) ? 4 : 8); + + // create gray line + nbgl_line_t *line = (nbgl_line_t *) nbgl_objPoolGet(LINE, layoutInt->layer); + line->lineColor = LIGHT_GRAY; + // align on bottom of the container + line->obj.alignment = BOTTOM_MIDDLE; + line->obj.area.width = SCREEN_WIDTH - 2 * 32; + line->obj.area.height = 4; + line->direction = HORIZONTAL; + line->thickness = 2; + line->offset = 2; + // set this line as child of the container + container->children[3] = (nbgl_obj_t *) line; + + return container; +} + +static nbgl_container_t *addSuggestionButtons(nbgl_layoutInternal_t *layoutInt, + uint8_t nbUsedButtons, + const char **buttonTexts, + int firstButtonToken, + tune_index_e tuneId, + bool compactMode) +{ + nbgl_container_t *suggestionsContainer; + layoutObj_t *obj; + + nbActiveButtons = nbUsedButtons; + suggestionsContainer = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + suggestionsContainer->layout = VERTICAL; + suggestionsContainer->obj.area.width = SCREEN_WIDTH; +#ifdef TARGET_STAX + // 2 rows of buttons with radius=32, and a intervale of 8px + suggestionsContainer->obj.area.height = 2 * SMALL_BUTTON_HEIGHT + INTERNAL_MARGIN; + suggestionsContainer->nbChildren = nbActiveButtons; + suggestionsContainer->children = (nbgl_obj_t **) nbgl_containerPoolGet( + NB_MAX_VISIBLE_SUGGESTION_BUTTONS, layoutInt->layer); +#else // TARGET_STAX + // 1 row of buttons + 24px + page indicator + suggestionsContainer->obj.area.height = SMALL_BUTTON_HEIGHT + 28; + // on Flex, the first child is used by the progress indicator, if more that 2 buttons + suggestionsContainer->nbChildren = nbActiveButtons + FIRST_BUTTON_INDEX; + suggestionsContainer->children = (nbgl_obj_t **) nbgl_containerPoolGet( + NB_MAX_VISIBLE_SUGGESTION_BUTTONS + 1, layoutInt->layer); + + // the suggestionsContainer is swipable on Flex + suggestionsContainer->obj.touchMask = (1 << SWIPED_LEFT) | (1 << SWIPED_RIGHT); + suggestionsContainer->obj.touchId = CONTROLS_ID; // TODO: change this value + obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) suggestionsContainer, 0, NBGL_NO_TUNE); + if (obj == NULL) { + return NULL; + } +#endif // TARGET_STAX + // put suggestionsContainer at 24px of the bottom of main container + suggestionsContainer->obj.alignmentMarginY = compactMode ? 12 : 24; + suggestionsContainer->obj.alignment = BOTTOM_MIDDLE; + + // create all possible suggestion buttons, even if not displayed at first + nbgl_objPoolGetArray(BUTTON, NB_MAX_SUGGESTION_BUTTONS, 0, (nbgl_obj_t **) &choiceButtons); + for (int i = 0; i < NB_MAX_SUGGESTION_BUTTONS; i++) { + obj = layoutAddCallbackObj( + layoutInt, (nbgl_obj_t *) choiceButtons[i], firstButtonToken + i, tuneId); + if (obj == NULL) { + return NULL; + } + + choiceButtons[i]->innerColor = BLACK; + choiceButtons[i]->borderColor = BLACK; + choiceButtons[i]->foregroundColor = WHITE; + choiceButtons[i]->obj.area.width = (SCREEN_WIDTH - 2 * BORDER_MARGIN - INTERNAL_MARGIN) / 2; + choiceButtons[i]->obj.area.height = SMALL_BUTTON_HEIGHT; + choiceButtons[i]->radius = RADIUS_32_PIXELS; + choiceButtons[i]->fontId = SMALL_BOLD_1BPP_FONT; + choiceButtons[i]->icon = NULL; + if ((i % 2) == 0) { +#ifdef TARGET_STAX + choiceButtons[i]->obj.alignmentMarginX = BORDER_MARGIN; + // second row 8px under the first one + if (i != 0) { + choiceButtons[i]->obj.alignmentMarginY = INTERNAL_MARGIN; + } + choiceButtons[i]->obj.alignment = NO_ALIGNMENT; +#else // TARGET_STAX + choiceButtons[i]->obj.alignmentMarginX = BORDER_MARGIN + INTERNAL_MARGIN; + if (i == 0) { + choiceButtons[i]->obj.alignment = TOP_LEFT; + } +#endif // TARGET_STAX + } + else { + choiceButtons[i]->obj.alignmentMarginX = INTERNAL_MARGIN; + choiceButtons[i]->obj.alignment = MID_RIGHT; + choiceButtons[i]->obj.alignTo = (nbgl_obj_t *) choiceButtons[i - 1]; + } + choiceButtons[i]->text = buttonTexts[i]; + choiceButtons[i]->obj.touchMask = (1 << TOUCHED); + choiceButtons[i]->obj.touchId = CONTROLS_ID + i; + // some buttons may not be visible + if (i < MIN(NB_MAX_VISIBLE_SUGGESTION_BUTTONS, nbActiveButtons)) { + suggestionsContainer->children[i + FIRST_BUTTON_INDEX] + = (nbgl_obj_t *) choiceButtons[i]; + } + } +#ifdef TARGET_FLEX + // on Flex, the first child is used by the progress indicator, if more that 2 buttons + nbgl_page_indicator_t *indicator + = (nbgl_page_indicator_t *) nbgl_objPoolGet(PAGE_INDICATOR, layoutInt->layer); + indicator->activePage = 0; + indicator->nbPages = (nbActiveButtons + 1) / 2; + indicator->obj.area.width = 184; + indicator->obj.alignment = BOTTOM_MIDDLE; + indicator->style = CURRENT_INDICATOR; + suggestionsContainer->children[PAGE_INDICATOR_INDEX] = (nbgl_obj_t *) indicator; + // also allocate the semi disc that may be displayed on the left or right of the full + // buttons + nbgl_objPoolGetArray(IMAGE, 2, 0, (nbgl_obj_t **) &partialButtonImages); + partialButtonImages[0]->buffer = &C_left_half_64px; + partialButtonImages[0]->obj.alignment = TOP_LEFT; + partialButtonImages[0]->foregroundColor = BLACK; + partialButtonImages[0]->transformation = VERTICAL_MIRROR; + partialButtonImages[1]->buffer = &C_left_half_64px; + partialButtonImages[1]->obj.alignment = TOP_RIGHT; + partialButtonImages[1]->foregroundColor = BLACK; + partialButtonImages[1]->transformation = NO_TRANSFORMATION; + updateSuggestionButtons(suggestionsContainer, 0, 0); +#endif // TARGET_STAX + + return suggestionsContainer; +} + +static nbgl_button_t *addConfirmationButton(nbgl_layoutInternal_t *layoutInt, + bool active, + const char *text, + int token, + tune_index_e tuneId, + bool compactMode) +{ + nbgl_button_t *button; + layoutObj_t *obj; + + button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); + obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) button, token, tuneId); + if (obj == NULL) { + return NULL; + } + + // put button at 24px of the bottom of main container + button->obj.alignmentMarginY = compactMode ? 12 : 24; + button->obj.alignment = BOTTOM_MIDDLE; + button->foregroundColor = WHITE; + if (active) { + button->innerColor = BLACK; + button->borderColor = BLACK; + button->obj.touchMask = (1 << TOUCHED); + button->obj.touchId = BOTTOM_BUTTON_ID; + } + else { + button->borderColor = LIGHT_GRAY; + button->innerColor = LIGHT_GRAY; + } + button->text = PIC(text); + button->fontId = SMALL_BOLD_1BPP_FONT; + button->obj.area.width = AVAILABLE_WIDTH; + button->obj.area.height = BUTTON_DIAMETER; + button->radius = BUTTON_RADIUS; + + return button; +} + /********************** * GLOBAL API FUNCTIONS **********************/ @@ -192,9 +462,18 @@ int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInf if (layout == NULL) { return -1; } + // footer must be empty + if (layoutInt->footerContainer != NULL) { + return -1; + } // create keyboard - keyboard = (nbgl_keyboard_t *) nbgl_objPoolGet(KEYBOARD, layoutInt->layer); + keyboard = (nbgl_keyboard_t *) nbgl_objPoolGet(KEYBOARD, layoutInt->layer); + keyboard->obj.area.width = SCREEN_WIDTH; + keyboard->obj.area.height = 3 * KEYBOARD_KEY_HEIGHT; + if (!kbdInfo->lettersOnly) { + keyboard->obj.area.height += KEYBOARD_KEY_HEIGHT; + } #ifdef TARGET_STAX keyboard->obj.alignmentMarginY = 64; #endif // TARGET_STAX @@ -205,18 +484,40 @@ int nbgl_layoutAddKeyboard(nbgl_layout_t *layout, const nbgl_layoutKbd_t *kbdInf keyboard->mode = kbdInfo->mode; keyboard->keyMask = kbdInfo->keyMask; keyboard->casing = kbdInfo->casing; - // set this new keyboard as child of the container - layoutAddObject(layoutInt, (nbgl_obj_t *) keyboard); - // return index of keyboard to be modified later on - return (layoutInt->container->nbChildren - 1); + // the keyboard occupies the footer + layoutInt->footerContainer = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + layoutInt->footerContainer->obj.area.width = SCREEN_WIDTH; + layoutInt->footerContainer->layout = VERTICAL; + layoutInt->footerContainer->children + = (nbgl_obj_t **) nbgl_containerPoolGet(1, layoutInt->layer); + layoutInt->footerContainer->obj.alignment = BOTTOM_MIDDLE; + layoutInt->footerContainer->obj.area.height + = keyboard->obj.area.height + keyboard->obj.alignmentMarginY; + layoutInt->footerContainer->children[0] = (nbgl_obj_t *) keyboard; + layoutInt->footerContainer->nbChildren = 1; + + // add footer to layout children + layoutInt->children[layoutInt->nbChildren] = (nbgl_obj_t *) layoutInt->footerContainer; + layoutInt->nbChildren++; + + // subtract footer height from main container height + layoutInt->container->obj.area.height -= layoutInt->footerContainer->obj.area.height; + + // create the 2 children for main container (to hold keyboard content) + layoutAddObject(layoutInt, (nbgl_obj_t *) NULL); + layoutAddObject(layoutInt, (nbgl_obj_t *) NULL); + + layoutInt->footerType = 0xFF; + + return layoutInt->footerContainer->obj.area.height; } /** * @brief Updates an existing keyboard on bottom of the screen, with the given configuration * * @param layout the current layout - * @param index index returned by @ref nbgl_layoutAddKeyboard() + * @param index index returned by @ref nbgl_layoutAddKeyboard() (unused) * @param keyMask mask of keys to activate/deactivate on keyboard * @param updateCasing if true, update keyboard casing with given value * @param casing casing to use @@ -235,9 +536,10 @@ int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, if (layout == NULL) { return -1; } + UNUSED(index); - // get keyboard at given index - keyboard = (nbgl_keyboard_t *) layoutInt->container->children[index]; + // get existing keyboard (in the footer container) + keyboard = (nbgl_keyboard_t *) layoutInt->footerContainer->children[0]; if ((keyboard == NULL) || (keyboard->obj.type != KEYBOARD)) { return -1; } @@ -255,7 +557,7 @@ int nbgl_layoutUpdateKeyboard(nbgl_layout_t *layout, * @brief function called to know whether the keyboard has been redrawn and needs a refresh * * @param layout the current layout - * @param index index returned by @ref nbgl_layoutAddKeyboard() + * @param index index returned by @ref nbgl_layoutAddKeyboard() (unused) * @return true if keyboard needs a refresh */ bool nbgl_layoutKeyboardNeedsRefresh(nbgl_layout_t *layout, uint8_t index) @@ -267,9 +569,10 @@ bool nbgl_layoutKeyboardNeedsRefresh(nbgl_layout_t *layout, uint8_t index) if (layout == NULL) { return -1; } + UNUSED(index); - // get keyboard at given index - keyboard = (nbgl_keyboard_t *) layoutInt->container->children[index]; + // get existing keyboard (in the footer container) + keyboard = (nbgl_keyboard_t *) layoutInt->footerContainer->children[0]; if ((keyboard == NULL) || (keyboard->obj.type != KEYBOARD)) { return -1; } @@ -283,6 +586,7 @@ bool nbgl_layoutKeyboardNeedsRefresh(nbgl_layout_t *layout, uint8_t index) /** * @brief Adds up to 4 black suggestion buttons under the previously added object + * @deprecated Use @ref nbgl_layoutAddKeyboardContent instead * * @param layout the current layout * @param nbUsedButtons the number of actually used buttons @@ -298,113 +602,25 @@ int nbgl_layoutAddSuggestionButtons(nbgl_layout_t *layout, int firstButtonToken, tune_index_e tuneId) { - layoutObj_t *obj; nbgl_container_t *container; nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + // if a centered info has be used for title, entered text is the second child + uint8_t enteredTextIndex = (layoutInt->container->nbChildren == 2) ? 0 : 1; LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddSuggestionButtons():\n"); if (layout == NULL) { return -1; } - nbActiveButtons = nbUsedButtons; - container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); - container->layout = VERTICAL; - container->obj.area.width = SCREEN_WIDTH; -#ifdef TARGET_STAX - // 2 rows of buttons with radius=32, and a intervale of 8px - container->obj.area.height = 2 * SMALL_BUTTON_HEIGHT + INTERNAL_MARGIN; - container->nbChildren = nbUsedButtons; - container->children = (nbgl_obj_t **) nbgl_containerPoolGet(NB_MAX_VISIBLE_SUGGESTION_BUTTONS, - layoutInt->layer); -#else // TARGET_STAX - // 1 row of buttons + 24px + page indicator - container->obj.area.height = SMALL_BUTTON_HEIGHT + 28; - // on Flex, the first child is used by the progress indicator, if more that 2 buttons - container->nbChildren = nbUsedButtons + FIRST_BUTTON_INDEX; - container->children = (nbgl_obj_t **) nbgl_containerPoolGet( - NB_MAX_VISIBLE_SUGGESTION_BUTTONS + 1, layoutInt->layer); - - // the container is swipable on Flex - container->obj.touchMask = (1 << SWIPED_LEFT) | (1 << SWIPED_RIGHT); - container->obj.touchId = CONTROLS_ID; // TODO: change this value - obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) container, 0, NBGL_NO_TUNE); - if (obj == NULL) { - return -1; + container = addSuggestionButtons( + layoutInt, nbUsedButtons, buttonTexts, firstButtonToken, tuneId, false); + // set this container as 2nd or 3rd child of the main layout container + layoutInt->container->children[enteredTextIndex + 1] = (nbgl_obj_t *) container; + if (layoutInt->container->children[enteredTextIndex] != NULL) { + ((nbgl_container_t *) layoutInt->container->children[enteredTextIndex]) + ->obj.alignmentMarginY + -= (container->obj.area.height + container->obj.alignmentMarginY + 20) / 2; } -#endif // TARGET_STAX - container->obj.alignmentMarginY = 24; - // align this control on top of keyboard (that must have been added just before) - container->obj.alignment = TOP_MIDDLE; - container->obj.alignTo = layoutInt->container->children[layoutInt->container->nbChildren - 1]; - - // create all possible suggestion buttons, even if not displayed at first - nbgl_objPoolGetArray(BUTTON, NB_MAX_SUGGESTION_BUTTONS, 0, (nbgl_obj_t **) &choiceButtons); - for (int i = 0; i < NB_MAX_SUGGESTION_BUTTONS; i++) { - obj = layoutAddCallbackObj( - layoutInt, (nbgl_obj_t *) choiceButtons[i], firstButtonToken + i, tuneId); - if (obj == NULL) { - return -1; - } - - choiceButtons[i]->innerColor = BLACK; - choiceButtons[i]->borderColor = BLACK; - choiceButtons[i]->foregroundColor = WHITE; - choiceButtons[i]->obj.area.width = (SCREEN_WIDTH - 2 * BORDER_MARGIN - INTERNAL_MARGIN) / 2; - choiceButtons[i]->obj.area.height = SMALL_BUTTON_HEIGHT; - choiceButtons[i]->radius = RADIUS_32_PIXELS; - choiceButtons[i]->fontId = SMALL_BOLD_1BPP_FONT; - choiceButtons[i]->icon = NULL; - if ((i % 2) == 0) { -#ifdef TARGET_STAX - choiceButtons[i]->obj.alignmentMarginX = BORDER_MARGIN; - // second row 8px under the first one - if (i != 0) { - choiceButtons[i]->obj.alignmentMarginY = INTERNAL_MARGIN; - } - choiceButtons[i]->obj.alignment = NO_ALIGNMENT; -#else // TARGET_STAX - choiceButtons[i]->obj.alignmentMarginX = BORDER_MARGIN + INTERNAL_MARGIN; - if (i == 0) { - choiceButtons[i]->obj.alignment = TOP_LEFT; - } -#endif // TARGET_STAX - } - else { - choiceButtons[i]->obj.alignmentMarginX = INTERNAL_MARGIN; - choiceButtons[i]->obj.alignment = MID_RIGHT; - choiceButtons[i]->obj.alignTo = (nbgl_obj_t *) choiceButtons[i - 1]; - } - choiceButtons[i]->text = buttonTexts[i]; - choiceButtons[i]->obj.touchMask = (1 << TOUCHED); - choiceButtons[i]->obj.touchId = CONTROLS_ID + i; - // some buttons may not be visible - if (i < MIN(NB_MAX_VISIBLE_SUGGESTION_BUTTONS, nbActiveButtons)) { - container->children[i + FIRST_BUTTON_INDEX] = (nbgl_obj_t *) choiceButtons[i]; - } - } -#ifndef TARGET_STAX - // on Flex, the first child is used by the progress indicator, if more that 2 buttons - nbgl_page_indicator_t *indicator - = (nbgl_page_indicator_t *) nbgl_objPoolGet(PAGE_INDICATOR, layoutInt->layer); - indicator->activePage = 0; - indicator->nbPages = (nbUsedButtons + 1) / 2; - indicator->obj.area.width = 184; - indicator->obj.alignment = BOTTOM_MIDDLE; - indicator->style = CURRENT_INDICATOR; - container->children[PAGE_INDICATOR_INDEX] = (nbgl_obj_t *) indicator; - // also allocate the semi disc that may be displayed on the left or right of the full buttons - nbgl_objPoolGetArray(IMAGE, 2, 0, (nbgl_obj_t **) &partialButtonImages); - partialButtonImages[0]->buffer = &C_left_half_64px; - partialButtonImages[0]->obj.alignment = TOP_LEFT; - partialButtonImages[0]->foregroundColor = BLACK; - partialButtonImages[0]->transformation = VERTICAL_MIRROR; - partialButtonImages[1]->buffer = &C_left_half_64px; - partialButtonImages[1]->obj.alignment = TOP_RIGHT; - partialButtonImages[1]->foregroundColor = BLACK; - partialButtonImages[1]->transformation = NO_TRANSFORMATION; - updateSuggestionButtons(container, 0, 0); -#endif // TARGET_STAX // set this new container as child of the main container layoutAddObject(layoutInt, (nbgl_obj_t *) container); @@ -415,9 +631,10 @@ int nbgl_layoutAddSuggestionButtons(nbgl_layout_t *layout, /** * @brief Updates the number and/or the text suggestion buttons created with @ref * nbgl_layoutAddSuggestionButtons() + * @deprecated Use @ref nbgl_layoutUpdateKeyboardContent instead * * @param layout the current layout - * @param index index returned by @ref nbgl_layoutAddSuggestionButtons() + * @param index index returned by @ref nbgl_layoutAddSuggestionButtons() (unused) * @param nbUsedButtons the number of actually used buttons * @param buttonTexts array of 4 strings for buttons (last ones can be NULL) * @return >= 0 if OK @@ -429,13 +646,15 @@ int nbgl_layoutUpdateSuggestionButtons(nbgl_layout_t *layout, { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; nbgl_container_t *container; + uint8_t enteredTextIndex = (layoutInt->container->nbChildren == 2) ? 0 : 1; LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateSuggestionButtons():\n"); if (layout == NULL) { return -1; } + UNUSED(index); - container = (nbgl_container_t *) layoutInt->container->children[index]; + container = (nbgl_container_t *) layoutInt->container->children[enteredTextIndex + 1]; if ((container == NULL) || (container->obj.type != CONTAINER)) { return -1; } @@ -491,6 +710,7 @@ int nbgl_layoutUpdateSuggestionButtons(nbgl_layout_t *layout, * @brief Adds a "text entry" area under the previously entered object. This area can be preceded * (beginning of line) by an index, indicating for example the entered world. A vertical gray line * is placed under the text. This text must be vertical placed in the screen with offsetY + * @deprecated Use @ref nbgl_layoutAddKeyboardContent instead * * @note This area is touchable * @@ -512,96 +732,55 @@ int nbgl_layoutAddEnteredText(nbgl_layout_t *layout, int token) { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; - nbgl_text_area_t *textArea; - nbgl_line_t *line; - layoutObj_t *obj; + nbgl_container_t *container; + uint8_t enteredTextIndex = (layoutInt->container->nbChildren == 2) ? 0 : 1; + bool compactMode = ((layoutInt->container->children[enteredTextIndex + 1] != NULL) + && (layoutInt->container->children[enteredTextIndex + 1]->type == BUTTON) + && (layoutInt->container->nbChildren == 3)); LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddEnteredText():\n"); if (layout == NULL) { return -1; } - - // create gray line - line = (nbgl_line_t *) nbgl_objPoolGet(LINE, layoutInt->layer); - line->lineColor = LIGHT_GRAY; - line->obj.alignmentMarginY = offsetY; - line->obj.alignTo = layoutInt->container->children[layoutInt->container->nbChildren - 1]; - line->obj.alignment = TOP_MIDDLE; - line->obj.area.width = SCREEN_WIDTH - 2 * 32; - line->obj.area.height = 4; - line->direction = HORIZONTAL; - line->thickness = 2; - line->offset = 2; - // set this new line as child of the main container - layoutAddObject(layoutInt, (nbgl_obj_t *) line); - - if (numbered) { - // create Word num typed text - textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); - textArea->textColor = BLACK; - snprintf(numText, sizeof(numText), "%d.", number); - textArea->text = numText; - textArea->textAlignment = CENTER; - textArea->fontId = LARGE_MEDIUM_1BPP_FONT; -#ifdef TARGET_STAX - textArea->obj.alignmentMarginY = 12; -#else // TARGET_STAX - textArea->obj.alignmentMarginY = 16; -#endif // TARGET_STAX - textArea->obj.alignTo = (nbgl_obj_t *) line; - textArea->obj.alignment = TOP_LEFT; -#ifdef TARGET_STAX - textArea->obj.area.width = 50; -#else // TARGET_STAX - textArea->obj.area.width = 66; -#endif // TARGET_STAX - textArea->obj.area.height = nbgl_getFontLineHeight(textArea->fontId); - // set this new text area as child of the main container - layoutAddObject(layoutInt, (nbgl_obj_t *) textArea); - } - - // create text area - textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); - textArea->textColor = grayedOut ? LIGHT_GRAY : BLACK; - textArea->text = text; - textArea->textAlignment = MID_LEFT; - textArea->fontId = LARGE_MEDIUM_1BPP_FONT; -#ifdef TARGET_STAX - textArea->obj.alignmentMarginY = 12; -#else // TARGET_STAX - textArea->obj.alignmentMarginY = 16; -#endif // TARGET_STAX - textArea->obj.alignTo = (nbgl_obj_t *) line; - textArea->obj.alignment = TOP_LEFT; - textArea->obj.area.width = line->obj.area.width; - if (numbered) { -#ifdef TARGET_STAX - textArea->obj.alignmentMarginX = 50; -#else // TARGET_STAX - textArea->obj.alignmentMarginX = 66; -#endif // TARGET_STAX - textArea->obj.area.width -= textArea->obj.alignmentMarginX; + UNUSED(offsetY); + + container + = addTextEntry(layoutInt, NULL, text, numbered, number, grayedOut, token, compactMode); + + // set this container as first or 2nd child of the main layout container + layoutInt->container->children[enteredTextIndex] = (nbgl_obj_t *) container; + + if (layoutInt->container->children[enteredTextIndex + 1] != NULL) { + if (layoutInt->container->children[enteredTextIndex + 1]->type == BUTTON) { + nbgl_button_t *button + = (nbgl_button_t *) layoutInt->container->children[enteredTextIndex + 1]; + container->obj.alignmentMarginY + -= (button->obj.area.height + button->obj.alignmentMarginY + + (compactMode ? 12 : 20)) + / 2; + } + else if (layoutInt->container->children[enteredTextIndex + 1]->type == CONTAINER) { + nbgl_container_t *suggestionContainer + = (nbgl_container_t *) layoutInt->container->children[enteredTextIndex + 1]; + container->obj.alignmentMarginY + -= (suggestionContainer->obj.area.height + suggestionContainer->obj.alignmentMarginY + + (compactMode ? 12 : 20)) + / 2; + } } - textArea->obj.area.height = nbgl_getFontLineHeight(textArea->fontId); - textArea->autoHideLongLine = true; - - obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) textArea, token, NBGL_NO_TUNE); - if (obj == NULL) { - return -1; + // if a centered info has be used for title, entered text is the second child and we have to + // adjust layout + if (layoutInt->container->nbChildren == 3) { + container->obj.alignmentMarginY += layoutInt->container->children[0]->area.height / 2; } - textArea->token = token; - textArea->obj.touchMask = (1 << TOUCHED); - textArea->obj.touchId = ENTERED_TEXT_ID; - - // set this new text area as child of the container - layoutAddObject(layoutInt, (nbgl_obj_t *) textArea); - // return index of text area to be modified later on - return (layoutInt->container->nbChildren - 1); + // return 0 + return 0; } /** * @brief Updates an existing "text entry" area, created with @ref nbgl_layoutAddEnteredText() + * @deprecated Use @ref nbgl_layoutUpdateKeyboardContent instead * * @param layout the current layout * @param index index of the text (return value of @ref nbgl_layoutAddEnteredText()) @@ -620,15 +799,22 @@ int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, bool grayedOut) { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_container_t *container; nbgl_text_area_t *textArea; + uint8_t enteredTextIndex = (layoutInt->container->nbChildren == 2) ? 0 : 1; LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateEnteredText():\n"); if (layout == NULL) { return -1; } + UNUSED(index); - // update main text area - textArea = (nbgl_text_area_t *) layoutInt->container->children[index]; + // update text entry area + container = (nbgl_container_t *) layoutInt->container->children[enteredTextIndex]; + if ((container == NULL) || (container->obj.type != CONTAINER)) { + return -1; + } + textArea = (nbgl_text_area_t *) container->children[2]; if ((textArea == NULL) || (textArea->obj.type != TEXT_AREA)) { return -1; } @@ -640,7 +826,7 @@ int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, // update number text area if (numbered) { // it is the previously created object - textArea = (nbgl_text_area_t *) layoutInt->container->children[index - 1]; + textArea = (nbgl_text_area_t *) layoutInt->container->children[1]; snprintf(numText, sizeof(numText), "%d.", number); textArea->text = numText; nbgl_redrawObject((nbgl_obj_t *) textArea, NULL, false); @@ -654,6 +840,7 @@ int nbgl_layoutUpdateEnteredText(nbgl_layout_t *layout, /** * @brief Adds a black full width confirmation button on top of the previously added keyboard. + * @deprecated Use @ref nbgl_layoutAddKeyboardContent instead * * @param layout the current layout * @param active if true, button is active, otherwise inactive (grayed-out) @@ -668,54 +855,33 @@ int nbgl_layoutAddConfirmationButton(nbgl_layout_t *layout, int token, tune_index_e tuneId) { - layoutObj_t *obj; nbgl_button_t *button; - nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + uint8_t enteredTextIndex = (layoutInt->container->nbChildren == 2) ? 0 : 1; + bool compactMode = (layoutInt->container->nbChildren == 3); LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddConfirmationButton():\n"); if (layout == NULL) { return -1; } - button = (nbgl_button_t *) nbgl_objPoolGet(BUTTON, layoutInt->layer); - obj = layoutAddCallbackObj(layoutInt, (nbgl_obj_t *) button, token, tuneId); - if (obj == NULL) { - return -1; - } - -#ifdef TARGET_STAX - button->obj.alignmentMarginY = BOTTOM_BORDER_MARGIN; -#else // TARGET_STAX - button->obj.alignmentMarginY = 24; -#endif // TARGET_STAX - button->obj.alignment = TOP_MIDDLE; - button->foregroundColor = WHITE; - if (active) { - button->innerColor = BLACK; - button->borderColor = BLACK; - button->obj.touchMask = (1 << TOUCHED); - button->obj.touchId = BOTTOM_BUTTON_ID; - } - else { - button->borderColor = LIGHT_GRAY; - button->innerColor = LIGHT_GRAY; + button = addConfirmationButton(layoutInt, active, text, token, tuneId, compactMode); + // set this button as second child of the main layout container + layoutInt->container->children[enteredTextIndex + 1] = (nbgl_obj_t *) button; + if (layoutInt->container->children[enteredTextIndex] != NULL) { + ((nbgl_container_t *) layoutInt->container->children[enteredTextIndex]) + ->obj.alignmentMarginY + -= (button->obj.area.height + button->obj.alignmentMarginY + (compactMode ? 12 : 20)) + / 2; } - button->text = PIC(text); - button->fontId = SMALL_BOLD_1BPP_FONT; - button->obj.area.width = AVAILABLE_WIDTH; - button->obj.area.height = BUTTON_DIAMETER; - button->radius = BUTTON_RADIUS; - button->obj.alignTo = layoutInt->container->children[layoutInt->container->nbChildren - 1]; - // set this new button as child of the container - layoutAddObject(layoutInt, (nbgl_obj_t *) button); - - // return index of button to be modified later on - return (layoutInt->container->nbChildren - 1); + // return 0 + return 0; } /** * @brief Updates an existing black full width confirmation button on top of the previously added keyboard. + * @deprecated Use @ref nbgl_layoutUpdateKeyboardContent instead * * @param layout the current layout * @param index returned value of @ref nbgl_layoutAddConfirmationButton() @@ -730,6 +896,9 @@ int nbgl_layoutUpdateConfirmationButton(nbgl_layout_t *layout, { nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; nbgl_button_t *button; + uint8_t enteredTextIndex = (layoutInt->container->nbChildren == 2) ? 0 : 1; + + UNUSED(index); LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateConfirmationButton():\n"); if (layout == NULL) { @@ -737,7 +906,7 @@ int nbgl_layoutUpdateConfirmationButton(nbgl_layout_t *layout, } // update main text area - button = (nbgl_button_t *) layoutInt->container->children[index]; + button = (nbgl_button_t *) layoutInt->container->children[enteredTextIndex + 1]; if ((button == NULL) || (button->obj.type != BUTTON)) { return -1; } @@ -756,5 +925,184 @@ int nbgl_layoutUpdateConfirmationButton(nbgl_layout_t *layout, nbgl_redrawObject((nbgl_obj_t *) button, NULL, false); return 0; } + +/** + * @brief Adds an area containing a potential title, a text entry and either confirmation + * or suggestion buttons, on top of the keyboard + * + * @param layout the current layout + * @param content structure containing the info + * @return the height of the area if OK + */ +int nbgl_layoutAddKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content) +{ + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_container_t *container; + bool compactMode = ((content->type == KEYBOARD_WITH_BUTTON) && (content->title != NULL)); + + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddKeyboardContent():\n"); + if (layout == NULL) { + return -1; + } + + container = addTextEntry(layoutInt, + content->title, + content->text, + content->numbered, + content->number, + content->grayedOut, + content->textToken, + compactMode); + + // set this container as first child of the main layout container + layoutInt->container->children[0] = (nbgl_obj_t *) container; + + if (content->type == KEYBOARD_WITH_SUGGESTIONS) { + nbgl_container_t *suggestionsContainer + = addSuggestionButtons(layoutInt, + content->suggestionButtons.nbUsedButtons, + content->suggestionButtons.buttons, + content->suggestionButtons.firstButtonToken, + content->tuneId, + compactMode); + // set this container as second child of the main layout container + layoutInt->container->children[1] = (nbgl_obj_t *) suggestionsContainer; + container->obj.alignmentMarginY + -= (suggestionsContainer->obj.area.height + suggestionsContainer->obj.alignmentMarginY + + (compactMode ? 12 : 20)) + / 2; + } + else if (content->type == KEYBOARD_WITH_BUTTON) { + nbgl_button_t *button = addConfirmationButton(layoutInt, + content->confirmationButton.active, + content->confirmationButton.text, + content->confirmationButton.token, + content->tuneId, + compactMode); + // set this button as second child of the main layout container + layoutInt->container->children[1] = (nbgl_obj_t *) button; + container->obj.alignmentMarginY + -= (button->obj.area.height + button->obj.alignmentMarginY + (compactMode ? 12 : 20)) + / 2; + } + + return layoutInt->container->obj.area.height; +} + +/** + * @brief Updates an area containing a potential title, a text entry and either confirmation + * or suggestion buttons, on top of the keyboard + * This area must have been built with @ref nbgl_layoutAddKeyboardContent, and the type must not + * change + * + * @param layout the current layout + * @param content structure containing the updated info + * @return the height of the area if OK + */ +int nbgl_layoutUpdateKeyboardContent(nbgl_layout_t *layout, nbgl_layoutKeyboardContent_t *content) +{ + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_container_t *container; + nbgl_text_area_t *textArea; + + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateKeyboardContent():\n"); + if (layout == NULL) { + return -1; + } + + // get top container from main container (it shall be the 1st object) + container = (nbgl_container_t *) layoutInt->container->children[0]; + + if (content->numbered) { + // get Word number typed text + textArea = (nbgl_text_area_t *) container->children[1]; + snprintf(numText, sizeof(numText), "%d.", content->number); + nbgl_redrawObject((nbgl_obj_t *) textArea, NULL, false); + } + + // get text area for entered text + textArea = (nbgl_text_area_t *) container->children[2]; + textArea->textColor = content->grayedOut ? LIGHT_GRAY : BLACK; + textArea->text = content->text; + nbgl_redrawObject((nbgl_obj_t *) textArea, NULL, false); + + if (content->type == KEYBOARD_WITH_SUGGESTIONS) { + nbActiveButtons = content->suggestionButtons.nbUsedButtons; + nbgl_container_t *suggestionsContainer + = (nbgl_container_t *) layoutInt->container->children[1]; + suggestionsContainer->nbChildren = nbActiveButtons + FIRST_BUTTON_INDEX; + + // update suggestion buttons + for (int i = 0; i < NB_MAX_SUGGESTION_BUTTONS; i++) { + choiceButtons[i]->text = content->suggestionButtons.buttons[i]; + // some buttons may not be visible + if (i < MIN(NB_MAX_VISIBLE_SUGGESTION_BUTTONS, nbActiveButtons)) { + if ((i % 2) == 0) { + choiceButtons[i]->obj.alignmentMarginX = BORDER_MARGIN; +#ifdef TARGET_STAX + // second row 8px under the first one + if (i != 0) { + choiceButtons[i]->obj.alignmentMarginY = INTERNAL_MARGIN; + } + choiceButtons[i]->obj.alignment = NO_ALIGNMENT; +#else // TARGET_STAX + if (i == 0) { + choiceButtons[i]->obj.alignment = TOP_LEFT; + } +#endif // TARGET_STAX + } + else { + choiceButtons[i]->obj.alignmentMarginX = INTERNAL_MARGIN; + choiceButtons[i]->obj.alignment = MID_RIGHT; + choiceButtons[i]->obj.alignTo = (nbgl_obj_t *) choiceButtons[i - 1]; + } + suggestionsContainer->children[i + FIRST_BUTTON_INDEX] + = (nbgl_obj_t *) choiceButtons[i]; + } + else { + suggestionsContainer->children[i + FIRST_BUTTON_INDEX] = NULL; + } + } + suggestionsContainer->forceClean = true; +#ifndef TARGET_STAX + // on Flex, the first child is used by the progress indicator, if more that 2 buttons + nbgl_page_indicator_t *indicator + = (nbgl_page_indicator_t *) suggestionsContainer->children[PAGE_INDICATOR_INDEX]; + indicator->nbPages = (nbActiveButtons + 1) / 2; + indicator->activePage = 0; + updateSuggestionButtons(suggestionsContainer, 0, 0); +#endif // TARGET_STAX + + nbgl_redrawObject((nbgl_obj_t *) suggestionsContainer, NULL, false); + } + else if (content->type == KEYBOARD_WITH_BUTTON) { + // update main text area + nbgl_button_t *button = (nbgl_button_t *) layoutInt->container->children[1]; + if ((button == NULL) || (button->obj.type != BUTTON)) { + return -1; + } + button->text = content->confirmationButton.text; + + if (content->confirmationButton.active) { + button->innerColor = BLACK; + button->borderColor = BLACK; + button->obj.touchMask = (1 << TOUCHED); + button->obj.touchId = BOTTOM_BUTTON_ID; + } + else { + button->borderColor = LIGHT_GRAY; + button->innerColor = LIGHT_GRAY; + } + nbgl_redrawObject((nbgl_obj_t *) button, NULL, false); + } + + // if the entered text doesn't fit, indicate it by returning 1 instead of 0, for different + // refresh + if (nbgl_getSingleLineTextWidth(textArea->fontId, content->text) > textArea->obj.area.width) { + return 1; + } + return 0; +} + #endif // NBGL_KEYBOARD #endif // HAVE_SE_TOUCH diff --git a/lib_nbgl/src/nbgl_layout_keypad.c b/lib_nbgl/src/nbgl_layout_keypad.c index 543d79770..2eeceec60 100644 --- a/lib_nbgl/src/nbgl_layout_keypad.c +++ b/lib_nbgl/src/nbgl_layout_keypad.c @@ -21,11 +21,15 @@ #include "glyphs.h" #include "os_pic.h" #include "os_helpers.h" -#include "lcx_rng.h" /********************* * DEFINES *********************/ +#ifdef TARGET_STAX +#define DIGIT_ICON C_round_24px +#else // TARGET_STAX +#define DIGIT_ICON C_pin_24 +#endif // TARGET_STAX /********************** * MACROS @@ -66,30 +70,54 @@ int nbgl_layoutAddKeypad(nbgl_layout_t *layout, keyboardCallback_t callback, boo if (layout == NULL) { return -1; } + // footer must be empty + if (layoutInt->footerContainer != NULL) { + return -1; + } // create keypad keypad = (nbgl_keypad_t *) nbgl_objPoolGet(KEYPAD, layoutInt->layer); keypad->obj.alignmentMarginY = 0; keypad->obj.alignment = BOTTOM_MIDDLE; keypad->obj.alignTo = NULL; + keypad->obj.area.width = SCREEN_WIDTH; + keypad->obj.area.height = 4 * KEYPAD_KEY_HEIGHT; keypad->borderColor = LIGHT_GRAY; keypad->callback = PIC(callback); keypad->enableDigits = true; keypad->enableBackspace = false; keypad->enableValidate = false; keypad->shuffled = shuffled; - // set this new keypad as child of the container - layoutAddObject(layoutInt, (nbgl_obj_t *) keypad); - // return index of keypad to be modified later on - return (layoutInt->container->nbChildren - 1); + // the keypad occupies the footer + layoutInt->footerContainer = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + layoutInt->footerContainer->obj.area.width = SCREEN_WIDTH; + layoutInt->footerContainer->layout = VERTICAL; + layoutInt->footerContainer->children + = (nbgl_obj_t **) nbgl_containerPoolGet(1, layoutInt->layer); + layoutInt->footerContainer->obj.alignment = BOTTOM_MIDDLE; + layoutInt->footerContainer->obj.area.height = keypad->obj.area.height; + layoutInt->footerContainer->children[layoutInt->footerContainer->nbChildren] + = (nbgl_obj_t *) keypad; + layoutInt->footerContainer->nbChildren++; + + // add to layout children + layoutInt->children[layoutInt->nbChildren] = (nbgl_obj_t *) layoutInt->footerContainer; + layoutInt->nbChildren++; + + // subtract footer height from main container height + layoutInt->container->obj.area.height -= layoutInt->footerContainer->obj.area.height; + + layoutInt->footerType = 0xFF; + + return layoutInt->footerContainer->obj.area.height; } /** * @brief Updates an existing keypad on bottom of the screen, with the given configuration * * @param layout the current layout - * @param index index returned by @ref nbgl_layoutAddKeypad() + * @param index index returned by @ref nbgl_layoutAddKeypad() (unused, for compatibility) * @param enableValidate if true, enable Validate key * @param enableBackspace if true, enable Backspace key * @param enableDigits if true, enable all digit keys @@ -111,9 +139,10 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, if (layout == NULL) { return -1; } + UNUSED(index); - // get existing keypad - keypad = (nbgl_keypad_t *) layoutInt->container->children[index]; + // get existing keypad (in the footer container) + keypad = (nbgl_keypad_t *) layoutInt->footerContainer->children[0]; if ((keypad == NULL) || (keypad->obj.type != KEYPAD)) { return -1; } @@ -132,6 +161,7 @@ int nbgl_layoutUpdateKeypad(nbgl_layout_t *layout, * @brief Adds a placeholder for hidden digits on top of a keypad, to represent the entered digits, * as full circles The placeholder is "underligned" with a thin horizontal line of the expected full * length + * @deprecated Use @ref nbgl_layouAddKeypadContent instead * * @note It must be the last added object, after potential back key, title, and keypad. Vertical * positions of title and hidden digits will be computed here @@ -169,18 +199,9 @@ int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits) container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); // pixels between each icon (knowing that the effective round are 18px large and the // icon 24px) - container->obj.area.width = nbDigits * C_round_24px.width + (nbDigits + 1) * space; + container->obj.area.width = nbDigits * DIGIT_ICON.width + (nbDigits - 1) * space; #ifdef TARGET_STAX - container->obj.area.height = 48; - // distance from digits to title is fixed to 20 px, except if title is more than 1 line and a - // back key is present - if ((layoutInt->container->nbChildren != 3) - || (layoutInt->container->children[1]->area.height == 32)) { - container->obj.alignmentMarginY = 20; - } - else { - container->obj.alignmentMarginY = 12; - } + container->obj.area.height = 50; #else // TARGET_STAX container->obj.area.height = 64; #endif // TARGET_STAX @@ -195,21 +216,16 @@ int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits) // create children of the container, as images (empty circles) nbgl_objPoolGetArray(IMAGE, nbDigits, layoutInt->layer, (nbgl_obj_t **) container->children); for (int i = 0; i < nbDigits; i++) { - nbgl_image_t *image = (nbgl_image_t *) container->children[i]; -#ifdef TARGET_STAX - image->buffer = &C_round_24px; -#else // TARGET_STAX - image->buffer = &C_pin_24; -#endif // TARGET_STAX - image->foregroundColor = WHITE; - image->obj.alignmentMarginX = space; + nbgl_image_t *image = (nbgl_image_t *) container->children[i]; + image->buffer = &DIGIT_ICON; + image->foregroundColor = WHITE; if (i > 0) { - image->obj.alignment = MID_RIGHT; - image->obj.alignTo = (nbgl_obj_t *) container->children[i - 1]; + image->obj.alignment = MID_RIGHT; + image->obj.alignTo = (nbgl_obj_t *) container->children[i - 1]; + image->obj.alignmentMarginX = space; } else { - image->obj.alignment = NO_ALIGNMENT; - image->obj.alignmentMarginY = (container->obj.area.height - C_round_24px.width) / 2; + image->obj.alignment = MID_LEFT; } } #ifdef TARGET_STAX @@ -234,6 +250,7 @@ int nbgl_layoutAddHiddenDigits(nbgl_layout_t *layout, uint8_t nbDigits) /** * @brief Updates an existing set of hidden digits, with the given configuration + * @deprecated Use @ref nbgl_layoutUpdateKeypadContent instead * * @param layout the current layout * @param index index returned by @ref nbgl_layoutAddHiddenDigits() @@ -285,16 +302,249 @@ int nbgl_layoutUpdateHiddenDigits(nbgl_layout_t *layout, uint8_t index, uint8_t image->foregroundColor = WHITE; } else { + image->buffer = &DIGIT_ICON; + image->foregroundColor = BLACK; + } + } + + nbgl_redrawObject((nbgl_obj_t *) image, NULL, false); + + return 0; +} + +/** + * @brief Adds an area with a title and a placeholder for hidden digits on top of a keypad, to + * represent the entered digits as small discs. On Stax, the placeholder is "underligned" with a + * thin horizontal line of the expected full length + * + * @note It shall be the only object added in the layout, beside a potential header and the keypad + * itself + * + * @param layout the current layout + * @param title the text to use on top of the digits + * @param hidden if set to true, digits appear as discs, otherwise as visible digits (given in text + * param) + * @param nbDigits number of digits to be displayed (only used if hidden is true) + * @param text only used if hidden is false + * @return the height of this area, if no error, < 0 otherwise + */ +int nbgl_layoutAddKeypadContent(nbgl_layout_t *layout, + const char *title, + bool hidden, + uint8_t nbDigits, + const char *text) +{ + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_container_t *container; + nbgl_text_area_t *textArea; + + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutAddKeypadContent():\n"); + if (layout == NULL) { + return -1; + } + // create a container, to store both title and "digits" (and line on Stax) + container = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + container->nbChildren = 2; +#ifdef TARGET_STAX + container->nbChildren++; // +1 for the line +#endif // TARGET_STAX + container->children = nbgl_containerPoolGet(container->nbChildren, layoutInt->layer); + container->obj.area.width = AVAILABLE_WIDTH; + container->obj.alignment = CENTER; + + // create text area for title + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = BLACK; + textArea->text = title; + textArea->textAlignment = CENTER; + textArea->fontId = SMALL_REGULAR_FONT; + textArea->wrapping = true; + textArea->obj.alignment = TOP_MIDDLE; + textArea->obj.area.width = AVAILABLE_WIDTH; + textArea->obj.area.height = nbgl_getTextHeightInWidth( + textArea->fontId, textArea->text, textArea->obj.area.width, textArea->wrapping); + container->children[0] = (nbgl_obj_t *) textArea; + container->obj.area.height += textArea->obj.area.height; + + if (hidden) { + nbgl_container_t *digitsContainer; + uint8_t space; + + if (nbDigits > KEYPAD_MAX_DIGITS) { + return -1; + } + // space between "digits" + if (nbDigits > 8) { + space = 4; + } + else { + space = 12; + } + + // create digits container, to store "discs" + digitsContainer = (nbgl_container_t *) nbgl_objPoolGet(CONTAINER, layoutInt->layer); + digitsContainer->nbChildren = nbDigits; + digitsContainer->children + = nbgl_containerPoolGet(digitsContainer->nbChildren, layoutInt->layer); + // pixels between each icon (knowing that the effective round are 18px large and the + // icon 24px) + digitsContainer->obj.area.width = nbDigits * DIGIT_ICON.width + (nbDigits - 1) * space; #ifdef TARGET_STAX - image->buffer = &C_round_24px; + digitsContainer->obj.area.height = 50; #else // TARGET_STAX - image->buffer = &C_pin_24; + digitsContainer->obj.area.height = 64; #endif // TARGET_STAX - image->foregroundColor = BLACK; + // align at the bottom of title + digitsContainer->obj.alignTo = container->children[0]; + digitsContainer->obj.alignment = BOTTOM_MIDDLE; + container->children[1] = (nbgl_obj_t *) digitsContainer; + container->obj.area.height += digitsContainer->obj.area.height; + + // create children of the container, as images (empty circles) + nbgl_objPoolGetArray( + IMAGE, nbDigits, layoutInt->layer, (nbgl_obj_t **) digitsContainer->children); + for (int i = 0; i < nbDigits; i++) { + nbgl_image_t *image = (nbgl_image_t *) digitsContainer->children[i]; + image->buffer = &DIGIT_ICON; + image->foregroundColor = WHITE; + if (i > 0) { + image->obj.alignment = MID_RIGHT; + image->obj.alignTo = (nbgl_obj_t *) digitsContainer->children[i - 1]; + image->obj.alignmentMarginX = space; + } + else { + image->obj.alignment = MID_LEFT; + } } } + else { + // create text area + textArea = (nbgl_text_area_t *) nbgl_objPoolGet(TEXT_AREA, layoutInt->layer); + textArea->textColor = BLACK; + textArea->text = text; + textArea->textAlignment = MID_LEFT; + textArea->fontId = LARGE_MEDIUM_1BPP_FONT; + textArea->obj.area.width = container->obj.area.width; + textArea->obj.area.height = nbgl_getFontLineHeight(textArea->fontId); + textArea->autoHideLongLine = true; + // align at the bottom of title + textArea->obj.alignTo = container->children[0]; + textArea->obj.alignment = BOTTOM_MIDDLE; + container->children[1] = (nbgl_obj_t *) textArea; + container->obj.area.height += textArea->obj.area.height; + } - nbgl_redrawObject((nbgl_obj_t *) image, NULL, false); + // set this new container as child of the main container + layoutAddObject(layoutInt, (nbgl_obj_t *) container); +#ifdef TARGET_STAX + nbgl_line_t *line; + // create gray line + line = (nbgl_line_t *) nbgl_objPoolGet(LINE, layoutInt->layer); + line->lineColor = LIGHT_GRAY; + line->obj.alignmentMarginY = 0; + line->obj.alignTo = NULL; + line->obj.alignment = BOTTOM_MIDDLE; + line->obj.area.width = container->obj.area.width; + line->obj.area.height = 4; + line->direction = HORIZONTAL; + line->thickness = 2; + line->offset = 2; + container->children[2] = (nbgl_obj_t *) line; +#endif // TARGET_STAX + + // return height of the area + return container->obj.area.height; +} + +/** + * @brief Updates an existing set of hidden digits, with the given configuration + * + * @param layout the current layout + * @param hidden if set to true, digits appear as discs, otherwise as visible digits (given in text + * param) + * @param nbActiveDigits number of "active" digits (represented by discs instead of circles) (only + * used if hidden is true) + * @param text only used if hidden is false + * + * @return >=0 if OK + */ +int nbgl_layoutUpdateKeypadContent(nbgl_layout_t *layout, + bool hidden, + uint8_t nbActiveDigits, + const char *text) +{ + nbgl_layoutInternal_t *layoutInt = (nbgl_layoutInternal_t *) layout; + nbgl_container_t *container; + nbgl_image_t *image; + + LOG_DEBUG(LAYOUT_LOGGER, "nbgl_layoutUpdateHiddenDigits(): nbActive = %d\n", nbActiveDigits); + if (layout == NULL) { + return -1; + } + + UNUSED(index); + + if (hidden) { + // get digits container (second child of the main container) + container = (nbgl_container_t *) ((nbgl_container_t *) layoutInt->container->children[0]) + ->children[1]; + // sanity check + if ((container == NULL) || (container->obj.type != CONTAINER)) { + return -1; + } + if (nbActiveDigits > container->nbChildren) { + return -1; + } + if (nbActiveDigits == 0) { + // deactivate the first digit + image = (nbgl_image_t *) container->children[0]; + if ((image == NULL) || (image->obj.type != IMAGE)) { + return -1; + } + image->foregroundColor = WHITE; + } + else { + image = (nbgl_image_t *) container->children[nbActiveDigits - 1]; + if ((image == NULL) || (image->obj.type != IMAGE)) { + return -1; + } + // if the last "active" is already active, it means that we are decreasing the number of + // active otherwise we are increasing it + if (image->foregroundColor == BLACK) { + // all digits are already active + if (nbActiveDigits == container->nbChildren) { + return 0; + } + // deactivate the next digit + image = (nbgl_image_t *) container->children[nbActiveDigits]; + image->foregroundColor = WHITE; + } + else { + image->buffer = &DIGIT_ICON; + image->foregroundColor = BLACK; + } + } + + nbgl_redrawObject((nbgl_obj_t *) image, NULL, false); + } + else { + // update main text area (second child of the main container) + nbgl_text_area_t *textArea + = (nbgl_text_area_t *) ((nbgl_container_t *) layoutInt->container->children[0]) + ->children[1]; + if ((textArea == NULL) || (textArea->obj.type != TEXT_AREA)) { + return -1; + } + textArea->text = text; + textArea->textColor = BLACK; + textArea->textAlignment = MID_LEFT; + nbgl_redrawObject((nbgl_obj_t *) textArea, NULL, false); + + // if the text doesn't fit, indicate it by returning 1 instead of 0, for different refresh + if (nbgl_getSingleLineTextWidth(textArea->fontId, text) > textArea->obj.area.width) { + return 1; + } + } return 0; } diff --git a/lib_nbgl/src/nbgl_obj.c b/lib_nbgl/src/nbgl_obj.c index 03315c5ff..e924df4b3 100644 --- a/lib_nbgl/src/nbgl_obj.c +++ b/lib_nbgl/src/nbgl_obj.c @@ -600,8 +600,7 @@ static void draw_radioButton(nbgl_radio_t *obj, nbgl_obj_t *prevObj, bool comput static void draw_progressBar(nbgl_progress_bar_t *obj, nbgl_obj_t *prevObj, bool computePosition) { #ifdef HAVE_SE_TOUCH - uint8_t stroke = 3; // 3 pixels for border - uint16_t levelWidth; + uint8_t stroke = 3; // 3 pixels for border if (computePosition) { compute_position((nbgl_obj_t *) obj, prevObj); @@ -618,7 +617,6 @@ static void draw_progressBar(nbgl_progress_bar_t *obj, nbgl_obj_t *prevObj, bool // inherit background from parent obj->obj.area.backgroundColor = obj->obj.parent->area.backgroundColor; - levelWidth = MIN(obj->obj.area.width * obj->state / 100, obj->obj.area.width); // if previous state is not nul, we will just draw the small added part if (obj->previousState == 0) { // draw external part if necessary @@ -633,31 +631,43 @@ static void draw_progressBar(nbgl_progress_bar_t *obj, nbgl_obj_t *prevObj, bool nbgl_drawRoundedRect( (nbgl_area_t *) obj, RADIUS_0_PIXELS, obj->obj.area.backgroundColor); } - // draw level - if (levelWidth > 0) { - uint16_t tmp_width = obj->obj.area.width; - obj->obj.area.width = levelWidth; - nbgl_drawRoundedRect((nbgl_area_t *) obj, RADIUS_0_PIXELS, obj->foregroundColor); - obj->obj.area.width = tmp_width; - } + } + else if (obj->previousState == 255) { + obj->state = 0; + } + + // Setup bar draw + nbgl_area_t barArea; + uint16_t barWidth = ((obj->state * obj->obj.area.width)) / 100; + int barDiffWith = barWidth - obj->previousWidth; + color_t barColor; + memcpy(&barArea, &obj->obj.area, sizeof(nbgl_area_t)); + + if (barDiffWith >= 0) { + // Drawing "forward" + barArea.x0 = obj->obj.area.x0 + obj->previousWidth; + barArea.width = barDiffWith; + barColor = obj->foregroundColor; } else { - uint16_t previousLevelWidth - = MIN(obj->obj.area.width * obj->previousState / 100, obj->obj.area.width); - nbgl_area_t area; - memcpy(&area, &obj->obj.area, sizeof(nbgl_area_t)); - // draw added level - area.width = levelWidth - previousLevelWidth; - if (area.width > 0) { - area.x0 += previousLevelWidth; - nbgl_drawRoundedRect((nbgl_area_t *) &area, RADIUS_0_PIXELS, obj->foregroundColor); - } - // reset previous state to be sure that in case of full redraw of the screen we redraw the - // full bar - obj->previousState = 0; - extendRefreshArea(&area); - objRefreshAreaDone = true; + // Drawing "backward" + barArea.x0 = obj->obj.area.x0 + obj->previousWidth + barDiffWith; + barArea.width = -barDiffWith; + barColor = obj->obj.area.backgroundColor; + } + + if (barDiffWith != 0) { + nbgl_drawRoundedRect((nbgl_area_t *) &barArea, RADIUS_0_PIXELS, barColor); } + + // reset previous state to be sure that in case of full redraw of the screen we redraw the + // full bar + if (obj->previousState) { + obj->previousState = 255; + } + obj->previousWidth = barWidth; + extendRefreshArea(&barArea); + objRefreshAreaDone = true; #else // HAVE_SE_TOUCH uint8_t stroke = 1; // 1 pixels for border uint16_t levelWidth; @@ -1036,13 +1046,7 @@ static void draw_qrCode(nbgl_qrcode_t *obj, nbgl_obj_t *prevObj, bool computePos */ static void draw_keyboard(nbgl_keyboard_t *obj, nbgl_obj_t *prevObj, bool computePosition) { -#ifdef HAVE_SE_TOUCH - obj->obj.area.width = SCREEN_WIDTH; - obj->obj.area.height = 3 * KEYBOARD_KEY_HEIGHT; - if (!obj->lettersOnly) { - obj->obj.area.height += KEYBOARD_KEY_HEIGHT; - } -#else // HAVE_SE_TOUCH +#ifndef HAVE_SE_TOUCH obj->obj.area.width = KEYBOARD_WIDTH; obj->obj.area.height = KEYBOARD_KEY_HEIGHT; #endif // HAVE_SE_TOUCH @@ -1073,10 +1077,7 @@ static void draw_keyboard(nbgl_keyboard_t *obj, nbgl_obj_t *prevObj, bool comput */ static void draw_keypad(nbgl_keypad_t *obj, nbgl_obj_t *prevObj, bool computePosition) { -#ifdef HAVE_SE_TOUCH - obj->obj.area.width = SCREEN_WIDTH; - obj->obj.area.height = 4 * KEYPAD_KEY_HEIGHT; -#else // HAVE_SE_TOUCH +#ifndef HAVE_SE_TOUCH obj->obj.area.height = KEYPAD_HEIGHT; obj->obj.area.width = KEYPAD_WIDTH; #endif // HAVE_SE_TOUCH diff --git a/lib_nbgl/src/nbgl_obj_keyboard.c b/lib_nbgl/src/nbgl_obj_keyboard.c index 1c88e4035..1e34d5312 100644 --- a/lib_nbgl/src/nbgl_obj_keyboard.c +++ b/lib_nbgl/src/nbgl_obj_keyboard.c @@ -558,7 +558,8 @@ void nbgl_keyboardTouchCallback(nbgl_obj_t *obj, nbgl_touchType_t eventType) if (keyboard->mode == MODE_LETTERS) { keyboardCase_t cur_casing = keyboard->casing; // if the casing mode was upper (not-locked), go back to lower case - if ((keyboard->casing == UPPER_CASE) && (firstIndex != SHIFT_KEY_INDEX)) { + if ((keyboard->casing == UPPER_CASE) && (firstIndex != SHIFT_KEY_INDEX) + && ((IS_KEY_MASKED(firstIndex)) == 0)) { keyboard->casing = LOWER_CASE; // just redraw, refresh will be done by client (user of keyboard) nbgl_redrawObject((nbgl_obj_t *) keyboard, NULL, false); diff --git a/lib_nbgl/src/nbgl_screen.c b/lib_nbgl/src/nbgl_screen.c index 6cc1e17aa..a81a6a5e8 100644 --- a/lib_nbgl/src/nbgl_screen.c +++ b/lib_nbgl/src/nbgl_screen.c @@ -404,6 +404,12 @@ int nbgl_screenPop(uint8_t screenIndex) // release used objects and containers nbgl_objPoolRelease(screenIndex); nbgl_containerPoolRelease(screenIndex); + + // special case when we pop the only modal and no real background is under it + if ((nbScreensOnStack == 1) && (screenStack[0].container.nbChildren == 0)) { + nbScreensOnStack = 0; + topOfStack = NULL; + } return 0; } @@ -423,6 +429,8 @@ int nbgl_screenReset(void) nbgl_objPoolRelease(screenIndex); nbgl_containerPoolRelease(screenIndex); } + screenStack[screenIndex].container.children = NULL; + screenStack[screenIndex].container.nbChildren = 0; } nbScreensOnStack = 0; topOfStack = NULL; diff --git a/lib_nbgl/src/nbgl_touch.c b/lib_nbgl/src/nbgl_touch.c index c43d9b333..3c39818fc 100644 --- a/lib_nbgl/src/nbgl_touch.c +++ b/lib_nbgl/src/nbgl_touch.c @@ -127,22 +127,32 @@ static nbgl_obj_t *getTouchedObject(nbgl_obj_t *obj, nbgl_touchStatePosition_t * } /** - * @brief Find first swipeable container object from screen - * + * @brief Get the deepest container that matches with input position and swipe + * @param obj Root object + * @param pos Position we are looking the container at + * @param detectedSwipe Swipe gesture we are looking for + * @return Pointer to obj if object exists, NULL otherwise */ - -static nbgl_obj_t *getSwipableObject(nbgl_obj_t *obj, nbgl_touchType_t detectedSwipe) +static nbgl_obj_t *getSwipableObjectAtPos(nbgl_obj_t *obj, + nbgl_touchStatePosition_t *pos, + nbgl_touchType_t detectedSwipe) { if (obj == NULL) { return NULL; } + // Check if pos position belongs to obj + if ((pos->x < obj->area.x0) || (pos->x >= (obj->area.x0 + obj->area.width)) + || (pos->y < obj->area.y0) || (pos->y >= (obj->area.y0 + obj->area.height))) { + return NULL; + } + if ((obj->type == SCREEN) || (obj->type == CONTAINER)) { nbgl_container_t *container = (nbgl_container_t *) obj; for (uint8_t i = 0; i < container->nbChildren; i++) { nbgl_obj_t *current = container->children[i]; if (current != NULL) { - nbgl_obj_t *child = getSwipableObject(current, detectedSwipe); + nbgl_obj_t *child = getSwipableObjectAtPos(current, pos, detectedSwipe); if (child) { return child; } @@ -155,11 +165,50 @@ static nbgl_obj_t *getSwipableObject(nbgl_obj_t *obj, nbgl_touchType_t detectedS return NULL; } +/** + * @brief Get the deepest container that matches with a swipe + * + * The returned container matches with: + * - First position in the swipe move + * - Last position in the swipe move + * - Detected swipe gesture + * + * @param first First position in the swipe move + * @param last Last position in the swipe move + * @param detectedSwipe Detected swipe gesture + * @return Pointer to obj if object exists, NULL otherwise + */ + +static nbgl_obj_t *getSwipableObject(nbgl_obj_t *obj, + nbgl_touchStatePosition_t *first, + nbgl_touchStatePosition_t *last, + nbgl_touchType_t detectedSwipe) +{ + if (obj == NULL) { + return NULL; + } + + nbgl_obj_t *first_obj = getSwipableObjectAtPos(obj, first, detectedSwipe); + + if (first_obj == NULL) { + return NULL; + } + + nbgl_obj_t *last_obj = getSwipableObjectAtPos(obj, last, detectedSwipe); + + // Swipable objects match + if (first_obj == last_obj) { + return first_obj; + } + + return NULL; +} + // Swipe detection #ifndef HAVE_HW_TOUCH_SWIPE -#define SWIPE_THRESHOLD_X 50 -#define SWIPE_THRESHOLD_Y 200 +#define SWIPE_THRESHOLD_X 10 +#define SWIPE_THRESHOLD_Y 20 #else // Mapping between nbgl_hardwareSwipe_t and nbgl_touchEvent_t const nbgl_touchType_t SWIPE_GESTURES[] = {[HARDWARE_SWIPE_UP] = SWIPED_UP, @@ -184,18 +233,18 @@ static nbgl_touchType_t nbgl_detectSwipe(nbgl_touchStatePosition_t *last, #else // Swipe is detected by software nbgl_touchType_t detected_swipe = NB_TOUCH_TYPES; - if ((last->y - first->y) >= SWIPE_THRESHOLD_Y) { - detected_swipe = SWIPED_DOWN; - } - else if ((first->y - last->y) >= SWIPE_THRESHOLD_Y) { - detected_swipe = SWIPED_UP; - } - else if ((last->x - first->x) >= SWIPE_THRESHOLD_X) { + if ((last->x - first->x) >= SWIPE_THRESHOLD_X) { detected_swipe = SWIPED_RIGHT; } else if ((first->x - last->x) >= SWIPE_THRESHOLD_X) { detected_swipe = SWIPED_LEFT; } + else if ((last->y - first->y) >= SWIPE_THRESHOLD_Y) { + detected_swipe = SWIPED_DOWN; + } + else if ((first->y - last->y) >= SWIPE_THRESHOLD_Y) { + detected_swipe = SWIPED_UP; + } return detected_swipe; #endif // HAVE_HW_TOUCH_SWIPE @@ -259,7 +308,8 @@ void nbgl_touchHandler(nbgl_touchStatePosition_t *touchStatePosition, uint32_t c if (swipe != NB_TOUCH_TYPES) { // Swipe detected - nbgl_obj_t *swipedObj = getSwipableObject(nbgl_screenGetTop(), swipe); + nbgl_obj_t *swipedObj = getSwipableObject( + nbgl_screenGetTop(), &firstTouchedPosition, &lastTouchedPosition, swipe); // if a swipable object has been found if (swipedObj) { applytouchStatePosition(swipedObj, swipe); diff --git a/lib_nbgl/src/nbgl_use_case.c b/lib_nbgl/src/nbgl_use_case.c index 820a0a2f8..e788851c1 100644 --- a/lib_nbgl/src/nbgl_use_case.c +++ b/lib_nbgl/src/nbgl_use_case.c @@ -87,9 +87,8 @@ typedef struct KeypadContext_s { uint8_t pinLen; uint8_t pinMinDigits; uint8_t pinMaxDigits; - uint32_t keypadIndex; - uint32_t hiddenDigitsIndex; nbgl_layout_t *layoutCtx; + bool hidden; } KeypadContext_t; #endif @@ -416,7 +415,9 @@ static void pageModalCallback(int token, uint8_t index) } else if (token == CHOICE_TOKEN) { if (index == 0) { - onModalConfirm(); + if (onModalConfirm != NULL) { + onModalConfirm(); + } } else { // display background, which should be the page where skip has been touched @@ -881,7 +882,8 @@ static void displayGenericContextPage(uint8_t pageIdx, bool forceFullRefresh) const nbgl_content_t *p_content = NULL; if ((navType == STREAMING_NAV) && (pageIdx >= bundleNavContext.reviewStreaming.stepPageNb)) { - return bundleNavReviewStreamingChoice(true); + bundleNavReviewStreamingChoice(true); + return; } if (navInfo.activePage == pageIdx) { @@ -1122,14 +1124,16 @@ static void updateKeyPad(bool add) redrawKeypad = true; } } - nbgl_layoutUpdateHiddenDigits( - keypadContext.layoutCtx, keypadContext.hiddenDigitsIndex, keypadContext.pinLen); + if (keypadContext.hidden == true) { + nbgl_layoutUpdateKeypadContent(keypadContext.layoutCtx, true, keypadContext.pinLen, NULL); + } + else { + nbgl_layoutUpdateKeypadContent( + keypadContext.layoutCtx, false, 0, (const char *) keypadContext.pinEntry); + } if (redrawKeypad) { - nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, - keypadContext.keypadIndex, - enableValidate, - enableBackspace, - enableDigits); + nbgl_layoutUpdateKeypad( + keypadContext.layoutCtx, 0, enableValidate, enableBackspace, enableDigits); } if ((!add) && (keypadContext.pinLen == 0)) { @@ -1153,8 +1157,7 @@ static void keypadCallback(char touchedKey) case VALIDATE_KEY: // Gray out keyboard / buttons as a first user feedback - nbgl_layoutUpdateKeypad( - keypadContext.layoutCtx, keypadContext.keypadIndex, false, false, true); + nbgl_layoutUpdateKeypad(keypadContext.layoutCtx, 0, false, false, true); nbgl_refreshSpecialWithPostRefresh(BLACK_AND_WHITE_FAST_REFRESH, POST_REFRESH_FORCE_POWER_ON); @@ -1172,6 +1175,66 @@ static void keypadCallback(char touchedKey) break; } } + +// called to create a keypad, with either hidden or visible digits +static void keypadGenericUseCase(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + bool hidden, + tune_index_e tuneId, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_layoutTouchCallback_t actionCallback) +{ + nbgl_layoutDescription_t layoutDescription = {0}; + nbgl_layoutHeader_t headerDesc = {.type = HEADER_BACK_AND_TEXT, + .separationLine = true, + .backAndText.token = backToken, + .backAndText.tuneId = tuneId, + .backAndText.text = NULL}; + int status = -1; + + if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) { + return; + } + + reset_callbacks(); + // reset the keypad context + memset(&keypadContext, 0, sizeof(KeypadContext_t)); + + // get a layout + layoutDescription.onActionCallback = actionCallback; + layoutDescription.modal = false; + layoutDescription.withLeftBorder = false; + keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription); + keypadContext.hidden = hidden; + + // set back key in header + nbgl_layoutAddHeader(keypadContext.layoutCtx, &headerDesc); + + // add keypad + status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled); + if (status < 0) { + return; + } + // add keypad content + status = nbgl_layoutAddKeypadContent( + keypadContext.layoutCtx, title, keypadContext.hidden, maxDigits, ""); + + if (status < 0) { + return; + } + + // validation pin callback + onValidatePin = validatePinCallback; + // pin code acceptable lengths + keypadContext.pinMinDigits = minDigits; + keypadContext.pinMaxDigits = maxDigits; + + nbgl_layoutDraw(keypadContext.layoutCtx); + nbgl_refreshSpecialWithPostRefresh(FULL_COLOR_CLEAN_REFRESH, POST_REFRESH_FORCE_POWER_ON); +} #endif static uint8_t nbgl_useCaseGetNbPagesForContent(const nbgl_content_t *content, uint8_t pageIdxStart) @@ -1773,7 +1836,7 @@ void nbgl_useCaseGenericConfiguration(const char *title, const nbgl_genericContents_t *contents, nbgl_callback_t quitCallback) { - return nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback); + nbgl_useCaseGenericSettings(title, initPage, contents, NULL, quitCallback); } /** @@ -1882,24 +1945,45 @@ void nbgl_useCaseStatus(const char *message, bool isSuccess, nbgl_callback_t qui void nbgl_useCaseReviewStatus(nbgl_reviewStatusType_t reviewStatusType, nbgl_callback_t quitCallback) { + const char *msg; + bool isSuccess; switch (reviewStatusType) { case STATUS_TYPE_OPERATION_SIGNED: - return nbgl_useCaseStatus("Operation signed", true, quitCallback); + msg = "Operation signed"; + isSuccess = true; + break; case STATUS_TYPE_OPERATION_REJECTED: - return nbgl_useCaseStatus("Operation rejected", false, quitCallback); + msg = "Operation rejected"; + isSuccess = false; + break; case STATUS_TYPE_TRANSACTION_SIGNED: - return nbgl_useCaseStatus("Transaction signed", true, quitCallback); + msg = "Transaction signed"; + isSuccess = true; + break; case STATUS_TYPE_TRANSACTION_REJECTED: - return nbgl_useCaseStatus("Transaction rejected", false, quitCallback); + msg = "Transaction rejected"; + isSuccess = false; + break; case STATUS_TYPE_MESSAGE_SIGNED: - return nbgl_useCaseStatus("Message signed", true, quitCallback); + msg = "Message signed"; + isSuccess = true; + break; case STATUS_TYPE_MESSAGE_REJECTED: - return nbgl_useCaseStatus("Message rejected", false, quitCallback); + msg = "Message rejected"; + isSuccess = false; + break; case STATUS_TYPE_ADDRESS_VERIFIED: - return nbgl_useCaseStatus("Address verified", true, quitCallback); + msg = "Address verified"; + isSuccess = true; + break; case STATUS_TYPE_ADDRESS_REJECTED: - return nbgl_useCaseStatus("Address verification\ncancelled", false, quitCallback); + msg = "Address verification\ncancelled"; + isSuccess = false; + break; + default: + return; } + nbgl_useCaseStatus(msg, isSuccess, quitCallback); } /** @@ -2641,7 +2725,45 @@ void nbgl_useCaseSpinner(const char *text) #ifdef NBGL_KEYPAD /** - * @brief draws a standard keypad modal page. The page contains + * @brief draws a standard keypad modal page with visible digits. It contains + * - a navigation bar at the top + * - a title for the pin code + * - a visible digit entry + * - the keypad at the bottom + * + * @note callbacks allow to control the behavior. + * backspace and validation button are shown/hidden automatically + * + * @param title string to set in pin code title + * @param minDigits pin minimum number of digits + * @param maxDigits maximum number of digits to be displayed + * @param backToken token used with actionCallback (0 if unused)) + * @param shuffled if set to true, digits are shuffled in keypad + * @param tuneId if not @ref NBGL_NO_TUNE, a tune will be played when back button is pressed + * @param validatePinCallback function calledto validate the pin code + * @param onActionCallback callback called on any action on the layout + */ +void nbgl_useCaseKeypadDigits(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_layoutTouchCallback_t actionCallback) +{ + keypadGenericUseCase(title, + minDigits, + maxDigits, + backToken, + shuffled, + false, + tuneId, + validatePinCallback, + actionCallback); +} +/** + * @brief draws a standard keypad modal page with hidden digits. It contains * - a navigation bar at the top * - a title for the pin code * - a hidden digit entry @@ -2659,65 +2781,24 @@ void nbgl_useCaseSpinner(const char *text) * @param validatePinCallback function calledto validate the pin code * @param onActionCallback callback called on any action on the layout */ -void nbgl_useCaseKeypad(const char *title, - uint8_t minDigits, - uint8_t maxDigits, - uint8_t backToken, - bool shuffled, - tune_index_e tuneId, - nbgl_pinValidCallback_t validatePinCallback, - nbgl_layoutTouchCallback_t actionCallback) -{ - nbgl_layoutDescription_t layoutDescription = {0}; - nbgl_layoutCenteredInfo_t centeredInfo = {0}; - int status = -1; - - if ((minDigits > KEYPAD_MAX_DIGITS) || (maxDigits > KEYPAD_MAX_DIGITS)) { - return; - } - - reset_callbacks(); - // reset the keypad context - memset(&keypadContext, 0, sizeof(KeypadContext_t)); - - // get a layout - layoutDescription.onActionCallback = actionCallback; - layoutDescription.modal = false; - layoutDescription.withLeftBorder = false; - keypadContext.layoutCtx = nbgl_layoutGet(&layoutDescription); - - // set navigation bar - nbgl_layoutAddProgressIndicator( - keypadContext.layoutCtx, 0, 0, (backToken != 0), backToken, tuneId); - - // add text description - centeredInfo.text1 = title; - centeredInfo.style = LARGE_CASE_INFO; - centeredInfo.onTop = true; - nbgl_layoutAddCenteredInfo(keypadContext.layoutCtx, ¢eredInfo); - - // add keypad - status = nbgl_layoutAddKeypad(keypadContext.layoutCtx, keypadCallback, shuffled); - if (status < 0) { - return; - } - keypadContext.keypadIndex = (unsigned int) status; - - // add hidden digits - status = nbgl_layoutAddHiddenDigits(keypadContext.layoutCtx, maxDigits); - if (status < 0) { - return; - } - keypadContext.hiddenDigitsIndex = (unsigned int) status; - - // validation pin callback - onValidatePin = validatePinCallback; - // pin code acceptable lengths - keypadContext.pinMinDigits = minDigits; - keypadContext.pinMaxDigits = maxDigits; - - nbgl_layoutDraw(keypadContext.layoutCtx); - nbgl_refreshSpecialWithPostRefresh(FULL_COLOR_CLEAN_REFRESH, POST_REFRESH_FORCE_POWER_ON); +void nbgl_useCaseKeypadPIN(const char *title, + uint8_t minDigits, + uint8_t maxDigits, + uint8_t backToken, + bool shuffled, + tune_index_e tuneId, + nbgl_pinValidCallback_t validatePinCallback, + nbgl_layoutTouchCallback_t actionCallback) +{ + keypadGenericUseCase(title, + minDigits, + maxDigits, + backToken, + shuffled, + true, + tuneId, + validatePinCallback, + actionCallback); } #endif // NBGL_KEYPAD diff --git a/lib_stusb_impl/u2f_impl.c b/lib_stusb_impl/u2f_impl.c index eec6fc688..634ae9a5e 100644 --- a/lib_stusb_impl/u2f_impl.c +++ b/lib_stusb_impl/u2f_impl.c @@ -332,7 +332,7 @@ void u2f_handle_cmd_msg(u2f_service_t *service, uint8_t *buffer, uint16_t length uint8_t p2 = buffer[OFFSET_P2]; #endif // U2F_PROXY_MAGIC - uint32_t dataLength = u2f_get_cmd_msg_data_length(buffer, length); + int dataLength = u2f_get_cmd_msg_data_length(buffer, length); if (dataLength < 0) { // invalid size u2f_message_reply(