The Nextion Menu – now filtered!

Introduction

When I started with this, I had no idea how this would evolve… Basically, the idea was to add a status bar to all or selected pages of any HMI project. Naturally coded in an optimized way with a minimal consumption of RAM and processor resources. While I was working on that, questions related to a Nextion HMI based menu to allow easier navigation arose in a Nextion related FaceBook user group. That’s how and why I continued, adding a menu “hamburger” to the status bar, allowing to display a customizable text based page navigation menu to whatever project.

To prevent any memory issues, especially on smaller Nextion HMI displays with 3.5k RAM, I continued making excessive use of GUI commands for this, so that the menu adds only a single transparent overlay page without components to any project, and a simple hotspot component to every page from which the menu could be called. User feedback taught me that this was great, but that the use cases were somewhat limited since the menu and its items were the same on every page.

Thus, in the next article, I extended the code to allow adding individual menu items, depending from which page the menu was called, to the “common” list of menu items. But while I was still working on that, new user feedback told me that it would be great to filter out the calling page from the menu, to leave more space for more meaningful items. I promised to do this, and that’s the topic of this article.

Looking back for a moment

Before we add functionality to existing code, we have always to make sure that we fully understand in every detail how that existing code works, so that adding new functions won’t break anything.

Our menu takes a configuration string in the variable cfg_menu which is organized like a csv file: one line for each menu item, separated by a new line (\r) control character. Each menu item consists of the id of the page to be called when the menu item is clicked, and the text to display. As an example, let’s think of a project with three pages, “Start” with id 0, “Setup” with id 2, and “About” with id 3. The transparent menu page, having the id 1 should naturally not be listed in the menu configuration, it will be called be a click on the menu “hamburger” in the status bar and show up according to the configuration.

From the above, it should be clear that the content of the cfg_menu variable should then be “0;Start\r2;Setup\r3;About”. The rendering of the menu items happens with the help of the spstr function, using “\r” as the separator, to loop through the menu items, and once again, using “;” as the separator, to split between the target page id and the menu text to display. Basically, in this example, we are dealing with a 3 x 2 two-dimensional array. The code to render (display) the menu uses the target page id for a comparison with the calling page id. If both are equal, the menu text will be preceded by a ” < ” to indicate that we’ll go back to the calling page. To other menu items a ” > ” is added to indicated that one is about to jump elsewhere.

Now, after displaying the menu, someone will click somewhere on the screen to select an item. We use the touch x- and y- coordinates found in tch0 and tch1. If someone clicks outside the rendered menu items, this will be handled like “cancel” and we jump back to the calling page. But when tch0 and tch1 fall into the menu area, we can subtract 21 from tch1 and (integer) divide the result by 21. This gives us 0 for the first item, 1 for the second, 2 for the third, and so on. With this “touched item index”, we can again look up in our cfg_menu array, because it’s identical to the line index, find the target page id and jump to it.

In the second, extended version, we added the opportunity for a page to add individual menu items by setting the global cfg_add variable. Everything there is then appended to cfg_menu. Let’s imagine, we inserted two more pages into our project, “Sub 1” with page id 3, and “Sub 2” with page id 4. The “About” page has now the page id 5 and we have to modify the content of cfg_menu accordingly. The “Sub 1” and “Sub 2” menu items should only be displayed when the menu is called from the Start page, but not from the others. Thus, we don’t put these into cfg_menu. Instead, we add the line to the event code of hotspot m0 on the Start page as follows:

menu.cfg_add.txt="3;Sub page 1\r4;Sub page 2"

This way, these two items will only be appended to the menu when called from the start page.

But again, with our y-coordinate formula, it is easy to “decode” the target page, because the touched item index corresponds still to the line index in cfg_menu.

And now to the filter!

Hiding the calling page from the menu is basically easy: While looping through cfg_menu, we check the target page index if it corresponds to the calling page index. And if these are equal, we do not longer add a ” < ” to the text, we simply do not display it. The problem is then that all following menu items “slip” one position upwards and their touched item index will be their expected index – 1.

The solution is to save the line index of the thus skipped menu item in a separate local variable component menu_skip during the rendering process:

    if(item_index.val==call_page.val)
    {
      //save the index of the skipped item
      menu_skip.val=tmp.val
    }else
    {
      //draw the menu item
      item_buf.txt=" > "+item_text.txt
      xstr menu_x.val,menu_y.val,menu_w.val,tb_h,1,tb_pco,tb_bco,0,1,1,item_buf.txt
      menu_y.val+=tb_h
      menu_y.val++
    }

Afterwards, when decoding the touch event, we’ll have to remember that all touched item indexes which are greater or equal than the value in menu_skip have to be incremented by 1 before looking up the target page:

  // add 1 for the index if a previous item was skipped
  if(item_index.val>=menu_skip.val)
  {
    item_index.val++
  }

And these are all the changes which our code needs to “hide” or “filter” dynamically the calling page out of the menu.

You are not required to make these changes by hand, the updated demo .hmi file is here: add_menu_filt.HMI

And no, this is not the end ofmy writing about menus. The next chapter is already in the works. Also based on user feedback, we’ll move on from the text based menu to a graphical menu with icons. Stay tuned!

Thank you for reading and happy Nextioning!

Questions, comments, critics, suggestions? Just send me an email to thierry (at) itead (dot) cc! 🙂