Developing Ledger application (part 2: Creating menu screens manually)
*These articles are my personal development logs, if I have wrong comprehension on some stuffs, feel free to let me know
**Get to know more about the blockchain technology we are developing: Zoobc in https://zoobc.com/
part 1: setting up development environment
part 3: Communicating with Ledger
part 4: Generating Public Key and signing data
If you try to explore applications available in the LedgerHQ github, you may find different ways of implementing menu or screens. Some applications manually declare components for each screen, while some other applications uses UX_FLOW
. I have been trying to use UX_FLOW for quite sometime but unfortunately I still can not use it. So because we are still in the initial development phase for Ledger application, I decided not being absorbed too much trying that. I would be able to revisit that once the main functionalities have been implemented.
But, I don’t find my self convenient declaring all the components manually for each screen of all of the operations in our ledger application. Not only that would complicate the logic for handling screen transition, but also it will clutter the source code and making it difficult for other developers to continue my codes later. So I decided to make my own menu screens my self.
In order to do that, it requires several things:
- Common screen components
- Logic that would handle screen transitions and interactions
To understand this better, let’s try creating an application that shows 3 states of screens:
- “Application Ready”
- “Version”
- and “Quit Application”
Creating Common Screen Components
Ledger has very limited memory, so it’s always better for us to minimize the memory usage so we don’t hit the ceiling when doing operations. To do our UI menu this way, we need to make a common template for all the 3 screens. Here’s the structure of the screen that we want to create:
But before we start declaring our common screen components, first we need to understand the structure of a single component first. Note that when declaring a single component, we usually anticipate whether that component is compiled for Ledger Blue or Ledger Nano S with #ifdef
directive. Here’s the structure of a single component:
// {
// {type, userid, x, y, width, height, stroke, radius, fill,
// fgcolor, bgcolor, font_id, icon_id},
// text,
// touch_area_brim,
// overfgcolor,
// overbgcolor,
// tap,
// out,
// over,
// },
Now we know the structure to declare a UI component. Next we need to decide what components is needed to create a common screen template:
- Background (without background, sometimes texts are overlapping)
- Top and bottom text (they need to be declared as separate components to make it easier for us to treat them individually)
- Right and Left Arrow
With that, we have our common screen components:
static const bagl_element_t bagl_ui_menu_template_nanos[] = {
{
// background
{BAGL_RECTANGLE, 0x00, 0, 0, 128, 32, 0, 0, BAGL_FILL, 0x000000,
0xFFFFFF, 0, 0},
NULL,
#ifdef TARGET_BLUE
0,
0,
0,
NULL,
NULL,
NULL,
#endif /* TARGET_BLUE */
},
{
// text line 1
{BAGL_LABELINE, 0x01, 0, 12, 128, 32, 0, 0, 0, 0xFFFFFF, 0x000000,
BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 0},
textTop,
#ifdef TARGET_BLUE
0,
0,
0,
NULL,
NULL,
NULL,
#endif /* TARGET_BLUE */
},
{
// text line 2
{BAGL_LABELINE, 0x83, 15, 26, 97, 12, 0x80 | 10, 0, 0, 0xFFFFFF, 0x000000,
BAGL_FONT_OPEN_SANS_REGULAR_11px | BAGL_FONT_ALIGNMENT_CENTER, 26},
textBottom,
#ifdef TARGET_BLUE
0,
0,
0,
NULL,
NULL,
NULL,
#endif /* TARGET_BLUE */
},
{
// left arrow
{BAGL_ICON, 0x00, 3, 12, 7, 7, 0, 0, 0, 0xFFFFFF, 0x000000, 0,
BAGL_GLYPH_ICON_LEFT},
NULL,
#ifdef TARGET_BLUE
0,
0,
0,
NULL,
NULL,
NULL,
#endif /* TARGET_BLUE */
},
{
// right arrow
{BAGL_ICON, 0x00, 117, 12, 8, 6, 0, 0, 0, 0xFFFFFF, 0x000000, 0,
BAGL_GLYPH_ICON_RIGHT},
NULL,
#ifdef TARGET_BLUE
0,
0,
0,
NULL,
NULL,
NULL,
#endif /* TARGET_BLUE */
},
};
Creating logic to handle screen transitions and interactions
Because we are creating an application with a number of capabilities, we would need to show information on the screen with respect the operation being performed and what interaction is performed by the user. For example, I want to show “Quit Application” when the user click the right button or left button (a menu with only 2 screens: “Application Ready” and “Quit Application”).
#define MENU_ENTRY_STEP_STATUS 0
#define MENU_ENTRY_STEP_VERSION 1
#define MENU_ENTRY_STEP_QUIT 2// UX Flow state
#define MENU_STATE_SIZE 2
static int menuState[MENU_STATE_SIZE];
static int menuStateNext[MENU_STATE_SIZE];
static int currentMenuMaxStep = 0;static void ui_screen_menu_entry(void)
{
menuStateNext[0] = MENU_ENTRY;
menuStateNext[1] = MENU_ENTRY_STEP_STATUS;
currentMenuMaxStep = 3;
ui_render();
}static void ui_render(void)
{
switch (menuStateNext[0])
{
case MENU_ENTRY:
switch (menuStateNext[1])
{
case MENU_ENTRY_STEP_STATUS:
render_menu_text_top("Application");
render_menu_text_bottom("is ready");
break;
case MENU_ENTRY_STEP_VERSION:
render_menu_text_top("Version");
render_menu_text_bottom(APPVERSION);
break;
case MENU_ENTRY_STEP_QUIT:
render_menu_text_top("Quit");
render_menu_text_bottom("Applicatiom");
break;
default:
break;
}
break;default:
break;
}
UX_DISPLAY(bagl_ui_menu_template_nanos, NULL);
}static void render_menu_text_top(char text[TEXT_TOP_SIZE])
{
for (int i = 0; i < TEXT_TOP_SIZE; i++)
{
textTop[i] = text[i];
}
}static void render_menu_text_bottom(char text[TEXT_BOTTOM_SIZE])
{
for (int i = 0; i < TEXT_BOTTOM_SIZE; i++)
{
textBottom[i] = text[i];
}
}static unsigned int
bagl_ui_menu_template_nanos_button(unsigned int button_mask,
unsigned int button_mask_counter)
{
uint32_t length = 0;
for (int i = 0; i < MENU_STATE_SIZE; i++)
{
menuState[i] = menuStateNext[i];
}switch (menuState[0])
{
case MENU_ENTRY:
switch (menuState[1])
{
case MENU_ENTRY_STEP_QUIT:
switch (button_mask)
{
case BUTTON_EVT_RELEASED | BUTTON_LEFT | BUTTON_RIGHT: // EXIT
io_seproxyhal_touch_exit(NULL);
return 0;
}
break;default:
break;
}
break;default:
break;
}// right and left default navigation
switch (button_mask)
{
case BUTTON_EVT_RELEASED | BUTTON_RIGHT:
if (menuStateNext[1] >= currentMenuMaxStep - 1)
{
menuStateNext[1] = 0;
}
else
{
menuStateNext[1]++;
}
ui_render();
break;case BUTTON_EVT_RELEASED | BUTTON_LEFT:
if (menuStateNext[1] <= 0)
{
menuStateNext[1] = currentMenuMaxStep - 1;
}
else
{
menuStateNext[1]--;
}
ui_render();
break;
}return 0;
}
The variables we declare in the beginning are used to keep the state of the screen we are currently in. you may notice that I have 2 identical variables `static int menuState[MENU_STATE_SIZE]` and `static int menuStateNext[MENU_STATE_SIZE]`. In normal application, we actually only need one of them to track the state of the screen. But when I implement it in Ledger Nano S, for some reason the device hangs. I suspect it’s part of the device security system, to not allow read and write a variable consecutively in some types of functions.
The method `ui_screen_menu_entry` is for initializing a entry screen, which will point to “Application ready” screen. `ui_render` is responsible to render specific texts with respect to the current screen state. And `bagl_ui_menu_template_nanos_button` will handle the button interaction in each scree. In the scene above, we only want to exit the application when we are at the screen “Quit Application” and both the left and right buttons are pressed.
There was my experience making the entry menu for Zoobc Ledger application. Let me know what you think and if you need more detailed explanation about any of the section.
Also, you can visit https://zoobc.com/ if you want to get to know more about blockchain technology that my company is going to launch.
Stay tune for the next part. Cheers! 🎉🎉🎉