m2tklib w/ glcd analog button input menu navigation

Hi guys,

Being a complete software programming noob i've been struggling with the m2tklib for quite some time now. I've only experience with the more basic programming tasks but now i'm trying to implement a user interface using a graphic lcd screen for which I use glcd in combination with the m2tklib.

Because i'm loosing pins on the arduino at a rapid pace I decided to put five buttons on one analog input using different resistor values for reading the buttons out.

The problem I face right now is to connect the buttons to the actual program, i've found only one example in which the analog input is used for multiple buttons using the m2tklib in which I could scroll through a list of multiple chars. But, whenever I try to create a menu system (don't even know if i'm doing this right, because also the alignment is pretty bad) I am not able to navigate through the actual menu or go into one of the sub menu's.

Anyone who has experience with this and would like to help me out on this one? :blush:

Complete code I'm working with at the moment:

#include <glcd.h>		// inform Arduino IDE that we will use GLCD library
#include "M2tk.h"
#include "utility/m2ghglcd.h"
#include "fonts/Arial14.h"         

int read_buttons()
{
  uint16_t adc_key_in = analogRead(5);
  if (adc_key_in > 880 && adc_key_in < 900) return M2_KEY_SELECT;
  if (adc_key_in > 757 && adc_key_in < 777) return M2_KEY_DATA_UP;
  if (adc_key_in > 498 && adc_key_in < 518) return M2_KEY_DATA_DOWN;
  if (adc_key_in > 224 && adc_key_in < 244) return M2_KEY_PREV;
  if (adc_key_in > 826 && adc_key_in < 846) return M2_KEY_NEXT;
  return M2_KEY_NONE;
}

uint8_t m2_es_arduino_analog_input(m2_p ep, uint8_t el_list)
{
  switch(el_list)
  {
    case M2_ES_MSG_GET_KEY:
      return read_buttons();
    case M2_ES_MSG_INIT:
      return 0;
  }
  return 0;
}


M2_EXTERN_VLIST(el_list1);
M2_EXTERN_VLIST(el_list2);
M2_EXTERN_VLIST(el_list3);

M2tk m2(&el_list1, m2_es_arduino, m2_eh_2bs, m2_gh_glcd_uffs);

void fn_Mode(m2_el_fnarg_p fnarg){
  m2.setRoot(&el_list2);
}

M2_LABEL(el_label1, NULL, "Main Menu");

M2_BUTTON(el_Mode, NULL, " Mode ", fn_Mode);
M2_BUTTON(el_TimeSettings, NULL, " Time settings ", fn_Mode);
M2_BUTTON(el_Manual, NULL, " Manual ", fn_Mode);
M2_BUTTON(el_Favourites, NULL, " Favourites ", fn_Mode);

M2_LIST(list1) = { &el_label1, &el_Mode};
M2_VLIST(el_list1, NULL, list1);


M2_LABEL(el_label2, NULL, "Select Mode");
M2_ROOT(el_ok2, NULL, " Return to Main menu ", &el_list1);
M2_LIST(list2) = { &el_label2, &el_Mode};
M2_VLIST(el_list2, NULL, list2);


M2_LABEL(el_label3, NULL, "Time settings");
M2_ROOT(el_ok3, NULL, " Return to Main menu ", &el_list1);
M2_LIST(list3) = { &el_label3, &el_TimeSettings};
M2_VLIST(el_list3, NULL, list3);


M2_ALIGN(top_centered_toplevel, "W64H10", &el_list1);

//const char * C1 = "Mode";
//M2_BUTTON(mode_button, "C1", &C1, bm_return_to_last_menu_cb);







//M2tk m2(&top_el, m2_es_arduino_analog_input, m2_eh_4bs, m2_gh_glcd_uffs);
//M2tk m2(&el_list1, m2_es_arduino_analog_input, m2_eh_6bs, m2_gh_glcd_uffs);


void setup() {
  m2.setFont(0, m2_System5x7 );
  
}

void loop() {
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey())
    m2.draw();
  m2.checkKey();
  }

I'm not sure but i'm suspecting I need to make a change to this part (uint8_t el_list) in order to make a connection between the keys and the menu. But I could be wrong offcourse (that would be very plausible I guess).

uint8_t m2_es_arduino_analog_input(m2_p ep, uint8_t el_list)
{
  switch(el_list)
  {
    case M2_ES_MSG_GET_KEY:
      return read_buttons();
    case M2_ES_MSG_INIT:
      return 0;
  }
  return 0;
}

Many thanks in advance! :slight_smile:

Why the gaps? Why not:

int read_buttons()
{
  uint16_t adc_key_in = analogRead(5);
  if (adc_key > 900) return M2_KEY_NONE;
  if (adc_key_in > 880) return M2_KEY_SELECT;
  if (adc_key_in > 826) return M2_KEY_NEXT;
  if (adc_key_in > 757) return M2_KEY_DATA_UP;
  if (adc_key_in > 498) return M2_KEY_DATA_DOWN;
  if (adc_key_in > 224) return M2_KEY_PREV;
  return M2_KEY_NONE;
}

The way you had it there were voltages might might be just in between that got ignored.


void loop() {
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey())
    m2.draw();
  m2.checkKey();
  }

I don't really understand that. What is it supposed to do?

Well I did make the gap because of environmental changes that might influence the values measured on the analog pin, making a gap of -10 and +10 from what I measure would be a safe margin (I think... :blush:)

According to the void loop;
From what I understand the loop does continuously check for hardware events such as pushing a button and the handle key tells the menu to act upon this event and redraw the menu.

But, this is also only what I understand from the m2tklib documentation. If I'm not able to get this to work, are there more "easy to use" interfacing libraries that work on the glcd library?

The problem I face right now is to connect the buttons to the actual program, i've found only one example in which the analog input is used for multiple buttons using the m2tklib in which I could scroll through a list of multiple chars. But, whenever I try to create a menu system (don't even know if i'm doing this right, because also the alignment is pretty bad) I am not able to navigate through the actual menu or go into one of the sub menu's.

Which is the exact example you are refering?
Is this example working for you? I mean, are you able to navigate with your buttons?
Maybe you can also post the some more code.

If I'm not able to get this to work, are there more "easy to use" interfacing libraries that work on the glcd library?

To my knowledge, m2tklib is the only interface lib for Arduino which supports graphic displays. At least, i wrote it because there was no other GUI available for the Arduino.

void loop() {
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey())
    m2.draw();
  m2.checkKey();
  }

"checkKey()" polls the hardware, that means in your case, the analog port is checked and some debouncing is done. Because of the debounce algorithm it is better to call checkKey() more often.
"handleKey()" will check if the internal event queue is not empty, will handle all events and finally return true, if the menu (e.g. cursor or interface) has been changed.
"draw()" will call the graphics procedures to rebuild the menu/dialog box. In this case the GLCD procedures are used, but u8glib, serial monitor and LiquidCrystal are also supported.

I agree that m2tklib needs some initial effort to understand the concept. But if you could tell me, what is working and what is not working correctly, then i am sure we find a solution.

Oliver

Because of the way that "loop" loops, effectively the code is:

  m2.checkKey();
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey())
    m2.draw();

It just seems odd to me to do three identical things in a row, and then work on the results. I mean, why not:

  m2.checkKey();
  m2.checkKey();
  m2.checkKey();
  m2.checkKey();
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey())
    m2.draw();

Or even:

  // do a checkKey three times
  for (byte i = 0; i < 3; i++)
    m2.checkKey();
  if ( m2.handleKey())
    m2.draw();

Usually

  m2.checkKey();
  if ( m2.handleKey())
    m2.draw();

should be fine.

Oliver

Hey Guys,

First of all thanks for the comments. And i've tried to fix the program for the last couple of days unfortunate without any success. I've been able to get the following code to work with the analog buttons:

#include <glcd.h>		// inform Arduino IDE that we will use GLCD library
#include "M2tk.h"
#include "utility/m2ghglcd.h"
#include "fonts/Arial14.h"         

int read_LCD_buttons_original()
{
  uint16_t adc_key_in = analogRead(5);
  if (adc_key_in > 880 && adc_key_in < 900) return M2_KEY_SELECT;
  if (adc_key_in > 757 && adc_key_in < 777) return M2_KEY_DATA_UP;
  if (adc_key_in > 498 && adc_key_in < 518) return M2_KEY_DATA_DOWN;
  if (adc_key_in > 224 && adc_key_in < 244) return M2_KEY_PREV;
  if (adc_key_in > 826 && adc_key_in < 846) return M2_KEY_NEXT;
  return M2_KEY_NONE;
}

uint8_t m2_es_arduino_analog_input(m2_p ep, uint8_t msg)
{
  switch(msg)
  {
    case M2_ES_MSG_GET_KEY:
      return read_LCD_buttons_original();
    case M2_ES_MSG_INIT:
      return 0;
  }
  return 0;
}


const char *selected = "Nothing";
const char *el_strlist_getstr(uint8_t idx, uint8_t msg) {
  const char *s = "";
  if  ( idx == 0 )
    s = "Configure";
  else if ( idx == 1 )
    s = "Banana";
  else if ( idx == 2 )
    s = "Peach";
  else if ( idx == 3 )
    s = "Pumpkin";
  else if ( idx == 4 )
    s = "Corn";
  else if ( idx == 5 )
    s = "Boe";
  else if ( idx == 6 )
    s = "Flip";
  else if ( idx == 7 )
    s = "Hoi";
  if (msg == M2_STRLIST_MSG_GET_STR) {
    /* nothing else todo, return the correct string */
  } else if ( msg == M2_STRLIST_MSG_SELECT ) {
    selected = s;
  }
  return s;
}

uint8_t el_strlist_first = 0;
uint8_t el_strlist_cnt = 8;

M2_LABEL(main_menu_label, NULL, "Main menu");
M2_SPACE(el_space1, "w1h10");
M2_LIST(list_title) = { &main_menu_label };
M2_HLIST(el_title_hlist, NULL, list_title);


M2_STRLIST(el_strlist, "l4w115", &el_strlist_first, &el_strlist_cnt, el_strlist_getstr);
M2_SPACE(el_space, "w1h1");
M2_VSB(el_strlist_vsb, "l4w5r1", &el_strlist_first, &el_strlist_cnt);
M2_LIST(list_strlist) = { &el_strlist, &el_space, &el_strlist_vsb };
M2_HLIST(el_strlist_hlist, NULL, list_strlist);

M2_SPACE(el_vspace, "w1h1");

M2_LABEL(el_label,NULL, "Selected:");
M2_LABELPTR(el_labelptr,NULL, &selected);
M2_LIST(list_label) = { &el_label, &el_labelptr };
M2_HLIST(el_label_hlist, NULL, list_label);

M2_LIST(list) = {&el_title_hlist, &el_strlist_hlist, &el_label_hlist }; //DIt geeft aan wat er gedisplayd word in dit geval
M2_VLIST(el_vlist, NULL, list);
M2_ALIGN(top_el, "-1|1", &el_vlist);

const char * C1 = "Mode";

M2_BUTTON(mode_button, "C1", &C1, bm_return_to_last_menu_cb);

//M2tk m2(&top_el, m2_es_arduino_analog_input, m2_eh_4bs, m2_gh_glcd_uffs);
M2tk m2(&top_el, m2_es_arduino_analog_input, m2_eh_6bs, m2_gh_glcd_uffs);



void setup() {
  m2.setFont(0, m2_System5x7 );
  
}

void loop() {
  m2.checkKey();
  m2.checkKey();
  if ( m2.handleKey() )
    m2.draw();
  m2.checkKey();
  }

Though, getting the analog buttons to work in a "real" menu seems to be impossible, therefore I've chosen to change the setup of the buttons so they all get their own unique input pin, this is not very efficient for the number of pins that remain but seems to be more stable as well, since the project will also be used in different weather circumstances. Cold/hot/rain/snow/..

Hopefully I will be able to get the buttons working in this new setup with the m2tklib interface menus.

Regards,
Niels

Hi

I had this analog buttons working once.
What values are printed on the serial monitor with this code? Will this match your code?

int sensorPin = 5;    // select the input pin for the potentiometer
int sensorValue = 0;  // variable to store the value coming from the sensor

void setup() {
  Serial.begin(9600);
}

void loop() {
  sensorValue = analogRead(sensorPin);    
  Serial.println(sensorValue);
  delay(1000);                  
}

Oliver

Hi

I have created an example based on your code, which works perfect in my environment. If course i had to adjust the analog values:

#include "U8glib.h"
#include "M2tk.h"
#include "utility/m2ghu8g.h"

U8GLIB_DOGM128 u8g(13, 11, 10, 9);		// SPI Com: SCK = 13, MOSI = 11, CS = 10, A0 = 9


int read_LCD_buttons_original()
{
  uint16_t adc_key_in = analogRead(A5);
  /*
  if (adc_key_in > 880 && adc_key_in < 900) return M2_KEY_SELECT;
  if (adc_key_in > 757 && adc_key_in < 777) return M2_KEY_DATA_UP;
  if (adc_key_in > 498 && adc_key_in < 518) return M2_KEY_DATA_DOWN;
  if (adc_key_in > 224 && adc_key_in < 244) return M2_KEY_PREV;
  if (adc_key_in > 826 && adc_key_in < 846) return M2_KEY_NEXT;
  */
  if (adc_key_in > 680 && adc_key_in < 720) return M2_KEY_SELECT;
  if (adc_key_in > 540 && adc_key_in < 580) return M2_KEY_PREV;
  if (adc_key_in > 380 && adc_key_in < 420) return M2_KEY_NEXT;
  return M2_KEY_NONE;
}

uint8_t m2_es_arduino_analog_input(m2_p ep, uint8_t msg)
{
  switch(msg)
  {
    case M2_ES_MSG_GET_KEY:
      return read_LCD_buttons_original();
    case M2_ES_MSG_INIT:
      return 0;
  }
  return 0;
}



const char *selected = "Nothing";
const char *el_strlist_getstr(uint8_t idx, uint8_t msg) {
  const char *s = "";
  if  ( idx == 0 )
    s = "Apple";
  else if ( idx == 1 )
    s = "Banana";
  else if ( idx == 2 )
    s = "Peach";
  else if ( idx == 3 )
    s = "Pumpkin";
  else if ( idx == 4 )
    s = "Corn";
  if (msg == M2_STRLIST_MSG_GET_STR) {
    /* nothing else todo, return the correct string */
  } else if ( msg == M2_STRLIST_MSG_SELECT ) {
    selected = s;
  }
  return s;
}

uint8_t el_strlist_first = 0;
uint8_t el_strlist_cnt = 5;

M2_STRLIST(el_strlist, "l2w90", &el_strlist_first, &el_strlist_cnt, el_strlist_getstr);
M2_SPACE(el_space, "w1h1");
M2_VSB(el_strlist_vsb, "l2w5r1", &el_strlist_first, &el_strlist_cnt);
M2_LIST(list_strlist) = { &el_strlist, &el_space, &el_strlist_vsb };
M2_HLIST(el_strlist_hlist, NULL, list_strlist);

M2_LABEL(el_label,NULL, "Selected:");
M2_LABELPTR(el_labelptr,NULL, &selected);
M2_LIST(list_label) = { &el_label, &el_labelptr };
M2_HLIST(el_label_hlist, NULL, list_label);

M2_LIST(list) = { &el_strlist_hlist, &el_label_hlist };
M2_VLIST(el_vlist, NULL, list);
M2_ALIGN(top_el, "-1|1W64H64", &el_vlist);





M2tk m2(&top_el, m2_es_arduino_analog_input, m2_eh_4bs, m2_gh_u8g_ffs);

// U8glib draw procedure: Just call the M2tklib draw procedure
void draw(void) {
    m2.draw();
}

// Arduino setup procedure (called only once)
void setup() {
  // Connect u8glib with m2tklib
  m2_SetU8g(u8g.getU8g(), m2_u8g_box_icon);

  // Assign u8g font to index 0
  m2.setFont(0, u8g_font_6x13);

}

// Arduino loop procedure
void loop() {
  m2.checkKey();
  if ( m2.handleKey() ) {
    u8g.firstPage();  
    do {
      draw();
    } while( u8g.nextPage() );
  }
}

Oliver

Hi Oliver,

Thanks works like a charm!
Though what if you'd like to call a new menu. For instance a menu list behind the "button" apple. I've tried to do so but without success.
I'm pretty close (I think :grin:) but there seems to be an issue with the amount of arguments.

uint8_t el_strlist_first = 0;
uint8_t el_strlist_cnt = 2;



M2_STRLIST(el_menuList, "l2w90", &el_strlist_first, &el_strlist_cnt, el_menuList_getstr);
M2_SPACE(el_space, "w1h1");
M2_VSB(el_menuList_vsb, "l2w5r1", &el_strlist_first, &el_strlist_cnt);
M2_LIST(list_strlist)={ &el_menuList, &el_space, &el_menuList_vsb};
M2_HLIST(el_strlist_hlist, NULL, list_strlist);
M2_ALIGN(top_el, "-1|1W64H64", &el_strlist_hlist);



M2tk m2(&top_el, m2_es_arduino, m2_eh_4bs, m2_gh_glcd_ffs);

uint8_t el_configureList_first = 0;
uint8_t el_configureList_cnt = 2;

M2_STRLIST(el_configureList, "l2w90", &el_configureList_first, &el_configureList_cnt, el_configureList_getstr);
M2_SPACE(el_space2, "w1h1");
M2_VSB(el_configureList_vsb, "l2w5r1", &el_configureList_first, &el_configureList_cnt);
M2_LIST(list_configurelist)={&el_configureList, &el_space, &el_configureList_vsb};
M2_HLIST(el_configure_hlist, NULL, list_configurelist);
//M2_ALIGN(top_el, "-1|1W64H64",  &el_configure_hlist);



const char *selected = "nothing";
const char *el_menuList_getstr(uint8_t idx, uint8_t callbackmsg){
 const char *s= "";
 int c;
 //int command;
  if(idx == 0)
  {
    s = "menu 1";
    c = 1;
   // accesMenu1();
    //m2.setRoot(&el_configureList);
  }
  else if(idx == 1){
    s = "menu 2";  
    c = 2;
    //accesMenu2();
 
  }
  if (callbackmsg == M2_STRLIST_MSG_GET_STR) {
    /* nothing else todo, return the correct string */
  } else if ( callbackmsg == M2_STRLIST_MSG_SELECT ) {
    selected = s;
    
    if (c==1){
      fn_configurepage();
      //Serial.println(c);
    }
    
  }
  return s;
}


////Configure Menu////
const char *el_configureList_getstr(uint8_t idx, uint8_t callbackmsg){
 const char *s= "";
 int c;
 //int command;
  if(idx == 0)
  {
    s = "Menu 1.1";
    c = 1;
   // accesMenu1();
    //m2.setRoot(&el_configureList);
  }
  else if(idx == 1){
    s = "Menu 1.2";  
    c = 2;
    //accesMenu2();
 
  }
  if (callbackmsg == M2_STRLIST_MSG_GET_STR) {
    /* nothing else todo, return the correct string */
  } else if ( callbackmsg == M2_STRLIST_MSG_SELECT ) {
    selected = s;
    
    Serial.println(c);
  }
  
  
  return s;
}

Thanks,
Niels

So your analog buttons are working now?

setRoot() is the correct procedure to assign a new menu. Also M2_ROOT() can be used for this job.
But it is hard to see your problem without knowing the exact error/warning and position in your code.

Oliver

Yes they are working :slight_smile:

Well i'm trying to create scrollable menus using the strlist. In my previous post I have two menu's created using two char lists (strlist).

  1. const char *el_menuList_getstr(uint8_t idx, uint8_t callbackmsg)
  2. const char *el_configureList_getstr(uint8_t idx, uint8_t callbackmsg)

The first menu has two chars in it: "Menu 1" and "Menu 2". What I try to do is to get into the second menu when selecting "Menu 1". In order to do so I've assigned an integer (c) to them and have them checked whenever I select "Menu 1" by which c = 1;. I check this by using the following if statement which is calling the function whenever integer c = 1 (menu 1).

if (callbackmsg == M2_STRLIST_MSG_GET_STR) {
    /* nothing else todo, return the correct string */
  } else if ( callbackmsg == M2_STRLIST_MSG_SELECT ) {
    selected = s;
    
    if (c==1){
      fn_configurepage();
      //Serial.println(c);
    }
    
  }

This is the actual function i'm calling when the if statement is true.

void fn_configurepage(m2_el_fnarg_p fnarg){
  m2.setRoot(&el_configureList);
}

Calling the function is where the error message comes in when compiling. Arduino gives me the following error statement:

too few arguments to function 'void fn_configurepate(_m2_el_fnarg*)'

together with these arguments in the verbose window in red:

strlist.cpp: In function 'const char* el_menuList_getstr(uint8_t, uint8_t)':
strlist:1: error: too few arguments to function 'void fn_configurepage(_m2_el_fnarg*)'
strlist:64: error: at this point in file
/Applications/Arduino.app/Contents/Resources/Java/libraries/glcd/include/gText.h: At global scope:
/Applications/Arduino.app/Contents/Resources/Java/libraries/glcd/include/gText.h:171: warning: 'FontRead' defined but not used

Niels

Why don't you simply call

m2.setRoot(&el_configureList);

?

Just to ensure that there is no other error: You wrote void fn_configurepate(_m2_el_fnarg*)

Another note from my side: M2_STRLIST() is already a quite complex element. Menus can be simply constructed by putting several M2_ROOT() element ito a M2_VLIST or M2_HLIST container. This is simpler, because no callback procedure is required.

Oliver

If I simply call

m2.setRoot(&el_configureList);

m2 isn't initialized yet and whenever I put the list after the initialization I get other errors as well.

sorry I did make a mistake in copying the errorcode from arduino, being fn_configurepage(_m2_el_fnarg*).

So you suggest not to use the strlist for a menu system but just go with individual buttons like I did in this example:

//----------MAIN MENU------------\\

M2_LABEL(el_mainMenuLabel, NULL, "Main menu");

M2_BUTTON(el_configure_btn, NULL, " Configure ", fn_configure_btn);
M2_BUTTON(el_favourites_btn, NULL, " Favourites ", fn_favourites_btn);
M2_BUTTON(el_resetPos_btn, NULL, " Reset position ", fn_resetPos_btn);
M2_LIST(mainMenuList) = { &el_mainMenuLabel, &el_configure_btn, &el_favourites_btn, &el_resetPos_btn };
M2_VLIST(el_mainMenuList, NULL, mainMenuList);
//M2_ALIGN(el_align_vlist, "-0|2", &el_mainMenuList);


//----------END of MAIN MENU-------------\\

//----------Configure---------------\\

M2_LABEL(el_configureLabel, NULL, "Config.|Select mode:");
//M2_LABEL(el_chooseMode, NULL, "Select mode: ");
M2_BUTTON(el_MSM_btn, NULL, " Move-Shoot-Move", fn_MSM_btn);
M2_BUTTON(el_shoot_btn, NULL, " Shoot only", fn_shoot_btn);
M2_BUTTON(el_video_btn, NULL, " Video dolly", fn_video_btn);
M2_SPACE(el_space, "w1h5");
M2_ROOT(el_configureRoot, NULL, " Back ", &el_mainMenuList);
M2_LIST(configureList) = { &el_configureLabel, /*&el_chooseMode,*/ &el_MSM_btn, &el_shoot_btn, &el_video_btn, &el_space, &el_configureRoot, };
M2_VLIST(el_configureList, NULL, configureList);


//----------End of Configure---------------\\

Scrolling seems impossible then or am I wrong?

Thanks for your time! I appreciate it! :slight_smile:
Niels

Hi

m2 isn't initialized yet and whenever I put the list after the initialization I get other errors as well.

Well, without seeing the full code this is hard to say. If you mean "declared" instead of "initialized", then maybe declaring as an external variable might help:

extern M2tk m2;

at the very beginning of your code, will make m2 available before the actual definition.

M2_BUTTON/M2_ROOT:
M2_BUTTON or the more simpler version M2_ROOT could be used to construct menues. However, you are right, scrolling is not possible.

For this element

M2_BUTTON(el_video_btn, NULL, " Video dolly", fn_video_btn);

i was thinking if M2_TOGGLE might be a better solution. But M2_TOGGLE needs a label along with it (it does not have a text):

uint8_t light_on = 0;
M2_LABEL(el_light_on_label, NULL, "Light On: ");
M2_TOGGLE(el_light_on_toggle, NULL, &light_on);

Of course you can combine both with M2_HLIST and replace the button from above with M2_VLIST:

M2_LIST(list_light_dialog) = { 
    &el_light_on_label, &el_light_on_toggle, 
};
M2_VLIST(el_light_vlist, NULL, list_light_dialog);

Note: You can nest containers, so a HLIST can contain a VLIST container.

Another idea is to use STRLIST with dynamic string rendering for toggle values. Here is some example code, which uses the addational string column of M2_STRLIST to display "*" or " " depending on the toggle value.

/* multi selection */

#define MULTI_SELECT_CNT 3
const char *multi_select_strings[MULTI_SELECT_CNT] = { "red", "green", "blue" };
uint8_t multi_select_status[MULTI_SELECT_CNT] = { 0, 0, 0};

uint8_t el_muse_first = 0;
uint8_t el_muse_cnt = MULTI_SELECT_CNT;

const char *el_muse_strlist_cb(uint8_t idx, uint8_t msg) {
  const char *s = "";
  if ( msg == M2_STRLIST_MSG_SELECT ) {
    if ( multi_select_status[idx] == 0 ) {
      multi_select_status[idx] = 1;
    }
    else {
      multi_select_status[idx] = 0;
    }
  }
  if ( msg == M2_STRLIST_MSG_GET_STR ) {
    s = multi_select_strings[idx];
  }
  if ( msg == M2_STRLIST_MSG_GET_EXTENDED_STR ) {
    if ( multi_select_status[idx] == 0 ) {
      s = " ";
    }
    else {
      s = "*";
    }
  }
  return s;  
}

M2_STRLIST(el_muse_strlist, "l3F0E10W46", &el_muse_first, &el_muse_cnt, el_muse_strlist_cb);
M2_ROOT(el_muse_goto_top, "f4", "Goto Top Menu", &top_el_tlsm);

M2_LIST(muse_list) = { 
    &el_muse_strlist, 
    &el_muse_goto_top,  
};
M2_VLIST(el_muse_vlist, "c2", muse_list);
M2_ALIGN(top_el_muse, "-1|1W64H64", &el_muse_vlist);

Just some ideas...

Oliver

Thanks Oliver, I think have some homework to do.:grin: Everything seems to work and I've got enough inspiration to carry on I guess. Only one minor thing I can't figure out is how to align multiple elements. Maybe there is a simple solution to this. But inserting multiple element names in the declaration doesn't seem to work. All menus need to be aligned the same way but for now only one menu can be aligned proper while all other elements appear on the bottom of the screen.

I don't want to be a pain, but I've been trying to solve all issues I experienced the last couple of days, most of them successful and some of them, yeah well... :sweat_smile: The forum seems to be a great tool for the remaining issues which I can't figure out myself but with the help of the forum are solvable!

Niels,

Hi Niels

A good automatic alignment is done by the M2_GRIDLIST container. There should be some examples which make use of this element. The M2_XYLIST could be also an option for manual placement of the elements.

Oliver

Going to give it a shot! Thanks

Dear Oliver, thank for this amazing library. Based on your idea:

/* multi selection */

#define MULTI_SELECT_CNT 3
const char *multi_select_strings[MULTI_SELECT_CNT] = { "red", "green", "blue" };
uint8_t multi_select_status[MULTI_SELECT_CNT] = { 0, 0, 0};

uint8_t el_muse_first = 0;
uint8_t el_muse_cnt = MULTI_SELECT_CNT;

const char *el_muse_strlist_cb(uint8_t idx, uint8_t msg) {
 const char *s = "";
 if ( msg == M2_STRLIST_MSG_SELECT ) {
   if ( multi_select_status[idx] == 0 ) {
     multi_select_status[idx] = 1;
   }
   else {
     multi_select_status[idx] = 0;
   }
 }
 if ( msg == M2_STRLIST_MSG_GET_STR ) {
   s = multi_select_strings[idx];
 }
 if ( msg == M2_STRLIST_MSG_GET_EXTENDED_STR ) {
   if ( multi_select_status[idx] == 0 ) {
     s = " ";
   }
   else {
     s = "*";
   }
 }
 return s; 
}

M2_STRLIST(el_muse_strlist, "l3F0E10W46", &el_muse_first, &el_muse_cnt, el_muse_strlist_cb);

I've tried to transform it, to implement a radio buttons selections but I see strange results. The first two members of the string work ok but any others on the list not. What am I doing wrong?

Here is my code, transformed for a 16x2 LCD:

#define MULTI_SELECT_CNT 4

const char *multi_select_strings[MULTI_SELECT_CNT] = { 
      "Output:00",
      "Output:01",
      "Output:02",
      "Output:03",
       
};

uint8_t multi_select_status[MULTI_SELECT_CNT] = { 0, 0, 0, 0};

const char *el_muse_strlist_cb(uint8_t idx, uint8_t msg) {
  const char *s = "";
  if ( msg == M2_STRLIST_MSG_GET_EXTENDED_STR ) {             //firt column of STRLIST
    s = multi_select_strings[idx];                                              // :output idx
  }
  
  if ( msg == M2_STRLIST_MSG_SELECT ) {                              //second column of STRLIST
    if ( multi_select_status[idx] == 0 ) {
      multi_select_status[idx] = 1;
      multi_select_status[!idx] = 0;          
    }
  }
  
  if ( msg == M2_STRLIST_MSG_GET_STR ) {
    if ( multi_select_status[idx] == 0 ) {
      s = "OFF";
      
    }
    else {
      s = "ON ";
    }
  }
  return s;
}

uint8_t el_muse_first = 0;
uint8_t el_muse_cnt = MULTI_SELECT_CNT;

M2_STRLIST(el_muse_strlist, "l2F0E40W14", &el_muse_first, &el_muse_cnt, el_muse_strlist_cb);


// m2 object and constructor
M2tk m2(&el_muse_strlist, m2_es_arduino, m2_eh_4bd, m2_gh_nlc);

Thank you very much in advance.