Библиотека для создания Меню на дисплеях

Тема в разделе "Глядите, что я сделал", создана пользователем DenisNP, 8 авг 2013.

  1. DenisNP

    DenisNP Нерд

    Всем привет. Для текущего проекта нужно было меню для дисплея 20х4, поэтому я набросал библиотеку, коей и делюсь с вами.

    Увидеть что это такое можно на гифке (осторожно, 14 мегабайт):
    [​IMG]

    Можно составить любую иерархию любой степени вложенности, где каждое подменю будет состоять из заголовка и любого количества пунктов (на экране 20х4 отображаются 3 пункта одновременно). Каждый пункт может выполнять либо переход к другому подменю, либо любое другое действие по желанию. Для управления экраном используется стандартная LiquidCrystal.

    Сама библиотека тут.

    Структура меню задается специальным массивом MItm items[] = {}, в котором есть элементы двух видов:
    Код (C):
    MItm(String name, int sub_idx, int prev_idx);
    Если три входных параметра, то этот элемент создает новое подменю с заголовком name и индексом sub_idx. Здесь еще есть prev_idx - индекс меню, которое является "верхним" по отношению к текущему меню. Для самого первого "верхнее" - оно же само, то есть индекс 0.

    Второй тип элементов содержит два параметра:
    Код (C):
    MItm(String name, int next_idx);
    Тут name это имя пункта в меню, а next_idx - индекс подменю, на которое нужно перейти при выборе этого пункта. Если такого подменю нет, то будет вызвана специальная функция, куда подастся на вход next_idx, чтобы знать, какой именно пункт нажали.

    Пример использования (тот который на гифке):
    Код (C):
    #include <MyMenu.h>
    #include <LiquidCrystal.h>

    LiquidCrystal lcd(3, 5, 10, 11, 12, 13); //инициализация дисплея

    #define NUM_ITEMS 32 //количество пунктов в меню включая все заголовки всех подменю

    MItm items[NUM_ITEMS] = {
      MItm("SHOPPING LIST",0,0), //этот пункт является заголовком подменю с индексом 0 (то есть главного меню)
      MItm("Fruits",1), //пункт главного меню, который при выборе переходит на подменю с индексом 1
      MItm("Veggies",2), //с индексом 2
      MItm("Other",3), //и с индексом 3 соответственно
      MItm("FRUITS LIST",1,0), //это подменю с индексом 1, а ноль - верхнее для него меню
      MItm("Apple",50), //подменю с индексом 50 нет, поэтому будет вызвана функция (ниже)
      MItm("Orange",60),
      MItm("Banana",70),
      MItm("Pear",80),
      MItm(" <<BACK",0), //вот так можно создавать кнопку "Назад"
      MItm("VEGGIES LIST",2,0), //подменю с индексом 2
      MItm("Tomato",90),
      MItm("Potato",100),
      MItm("Smth else",110),
      MItm(" <<BACK",0),
      MItm("Other",3,0),
      MItm("Meat",4),
      MItm("Fish",5),
      MItm(" <<BACK",0),
      MItm("MEAT",4,3), //здесь уже верхним меню является подменю с индексом 3
      MItm("Chicken",6),
      MItm("Bacon",7),
      MItm("Beef",8),
      MItm("SHOW FISH",5),
      MItm(" <<BACK",3),
      MItm(" <<MAIN",0),
      MItm("FISH",5,3),
      MItm("Clownfish",9),
      MItm("Shark",10),
      MItm("SHOW MEAT",4),
      MItm(" <<BACK",3),
      MItm(" <<MAIN",0)
    };

    Menu menu(items,NUM_ITEMS,&lcd,menuCallback); //так создается меню (подробнее ниже)

    void setup() {
      //настраиваем кнопки
      pinMode(42,INPUT);
      pinMode(44,INPUT);
      pinMode(46,INPUT);
      pinMode(48,INPUT);

      //запускаем экран
      lcd.begin(20,4);

      //отображаем на экране главное меню
      menu.goMain();
    }

    void loop() {
      if(digitalRead(42) == HIGH){ //нажата кнопка "назад"
        menu.goBack(); //функция возвращает на предыдущий уровень меню
        delay(100);
      }
      if(digitalRead(44) == HIGH){ //нажата кнопка "вниз"
        menu.goDown(); //функция перемещает курсор на один пункт вниз
        delay(100);
      }
      if(digitalRead(46) == HIGH){ //нажата кнопка "вверх"
        menu.goUp(); //функция перемещает курсор на один пункт вверх
        delay(100);
      }
      if(digitalRead(48) == HIGH){ //нажата кнопка "вперед" или "выбрать пункт"
        menu.goNext(); //функция переходит на новый уровень подменю или вызывает callback
        delay(100);
      }

    delay(100);
    }

    void menuCallback(int idx){
    //эта функция будет вызвана, если подменю с индексом idx нет
    //и нужно совершить действие, а не уходить на еще один уровень в меню

      if(idx < 10){ //если idx < 10 делаем одно
        lcd.clear();
        lcd.setCursor(2,1);
        lcd.print("NO MEAT n FISH!");
        delay(1000);
      }else if(idx == 10){ //если выбран пункт "акула" делаем другое :)
        int i=16;
        while(i >= 0){
          lcd.clear();
          lcd.setCursor(0,2);
          lcd.print("-^-^-^-^-^-^-^-^-^-^");
          lcd.setCursor(i,1);
          lcd.print("/l");
          delay(300);
          i--;
        }
      }else{ //иначе просто пишем что было выбрано
        lcd.clear();
        lcd.setCursor(2,2);
        lcd.print(String(idx)+" pressed");
        delay(1000);
      }
      menu.goLast(); //после некоторой паузы вот этой функцией возвращаемся на последнее показанное меню
    }
    Хочу обратить внимание, что в функции menu, во-первых, используется &lcd, а не просто lcd,
    Во-вторых у функции есть еще два параметра, со значениями по умолчанию:
    Код (C):
    menu(MItm items,int num_items,LiquidCrystal* lcd,void callback(int),int num_rows = 4,String cursor = ">");
    Здесь num_rows это количество строк в вашем дисплее, по-умолчанию 4
    cursor это курсор, которым будет отмечаться пункт в меню, по-умолчанию ">", может быть любой длины

    Из не показанного в примере есть еще такие функции:
    Код (C):
    menu.goSub(int sub_idx);
    - переходит сразу на подменю с индексом sub_idx
    Код (C):
    menu.goItem(int item_idx);
    - переходит на подменю, содержащее пункт item_idx (индекс пункта в массиве items от нуля соответственно), и выделяет этот пункт

    Надеюсь, кому-то пригодится :)
     
    Последнее редактирование: 20 фев 2014
  2. HighDigital

    HighDigital Гик

    Хороший труд!
     
  3. Vir

    Vir Гик

    Отлично! Спасибо за труд.
     
  4. id90

    id90 Нерд

    может немножко не в тему.... но для тех кто не раздобыл себе экранчик есть такая библиотека TVout использовать телевизор в качестве экрана
     
    Neon_1 и Troll нравится это.
  5. HighDigital

    HighDigital Гик

    Да, ещё по-мойму к этой же библии прилагается шилд: Video Experemental Shield
    ну и другие :)
     
  6. id90

    id90 Нерд

    я так подключал все работает.
     
  7. HighDigital

    HighDigital Гик

    да, можно и без шилда, через видеовыход.
     
  8. Unixon

    Unixon Оракул Модератор

    +много за гитхаб
     
  9. Festour

    Festour Нерд

    Можете перезалить гифку?
     
  10. riturnel

    riturnel Нуб

    Здорово!
    А с LiquidCrystal_I2C будет работать?
     
  11. DenisNP

    DenisNP Нерд

    Если все методы те же, то должно.

    Готово. Контакт постоянно меняет статический адрес :(
     
  12. KingPin

    KingPin Нуб

    Подскажите, как добавлять подменю динамически в цикле?
    спасибо
     
  13. Зайдите на https://vk.com/docs
    Получите ссылку на документ вида
    https://vk.com/docЦИФРЫ_ЦИФРЫ
    Добавьте в конце ссылки ?api=1

    Получится так: https://vk.com/docЦИФРЫ_ЦИФРЫ?api=1
    Например: https://vk.com/doc232144796_257749008?api=1 (просто случайная картинка из поиска)
    Готово, можно вставлять ссылку на картинку!
     
    Последнее редактирование: 4 янв 2014
  14. Спасибо за библиотеку!
     
    Последнее редактирование: 6 янв 2014
  15. pasha_dv

    pasha_dv Нуб

    Прекрасная библиотека! Спасибо! Быстро разобрался и сделал меню, но столкнулся со странностью, если скетч становится больше 10Кбайт - начинаются проблемы с отображением пунктов. Причем с конца меню, т.е. сначала исчезают последние пункты. Если нажимать кнопки вслепую - то все отрабатывается, т.е страдает только отображение.
     
  16. DenisNP

    DenisNP Нерд

    Ух-ты, круто, спасибо :)
     
  17. DenisNP

    DenisNP Нерд

    Не сталкивался с таким. Нужно тестировать на разных платах и дисплеях.
     
  18. Egen_M

    Egen_M Нерд

    Хорошая чувствуется программа, вот только не идет она у меня на LiquidCrystal_I2C.
    MyMenu:61: error: no matching function for call to 'Menu::Menu(MItm [32], int, LiquidCrystal_I2C*, void (&)(int))'
    А с группой кнопок на аналоговом порту она работать будет?
     
    Didal_(RUS74) нравится это.
  19. Didal_(RUS74)

    Didal_(RUS74) Нуб

    У меня 5 кнопок на аналоговом входе - работает. Только проблемы с отображениями на дисплеях. Если все исправлю - выложу =))
    По поводу работы с I2C - надо, как я понял, править библиотеку. Если бы смог - исправил бы сам.

    Но все равно автору спасибо!
     
  20. nioli

    nioli Нуб

    Спасибо! То что надо!
    Чтобы работала с LiquidCrystal_I2C (Проверял с 1602 по IC2 ----работает), нужно исправить файл

    MyMenu.h:
    13 строка #include <LiquidCrystal_I2C.h>
    31 строка Menu(MItm *_items, int _num_items, LiquidCrystal_I2C* _lcd, void (*_callback)(int), int _num_rows, String _cursor);
    46 строка LiquidCrystal_I2C *lcd;

    MyMenu.cpp:
    48 строка #include <LiquidCrystal_I2C.h>
    79 строка Menu::Menu(MItm *_items, int _num_items, LiquidCrystal_I2C* _lcd, void (*_callback)(int), int _num_rows, String _cursor)

    В файле примера строку
    Menu menu(items,NUM_ITEMS,&lcd,menuCallback);// заменить на
    Menu menu(items, NUM_ITEMS, &lcd, menuCallback,NUM_ROWS, ">");
    где NUM_ROWS заранее объявить 2 (#define NUM_ROWS 2) для 1602 или 4 (#define NUM_ROWS 4) для 2004 . Указатель ">" можно заменить на любой по вкусу в этой строке.

    По поводу поста #15 (проблемы с отображением пунктов, если скетч становится больше 10Кбайт - начинаются проблемы с отображением пунктов) проблемы не с библиотекой, а с нехваткой памяти ОЗУ, в моём случае проблемы были когда занятость памяти ОЗУ достигала 55% .Проверял добавлением пунктов меню и количеством букв в меню. Выход смена платы UNO(2К) на МЕГА 2560 (8К) или перенос памяти занимаемой МЕНЮ на внешнюю память. НО тут надо думать, как сделать и будет ли работать.
     
    Последнее редактирование: 22 апр 2015
    9xA59kK нравится это.