Доброго времени суток, уважаемые форумчане!
Я новичок на форуме. На старом форуме поиском я нашел тему " Создание многоуровневого графического меню" ЕвгенийП выложил код древовидного меню. Задать вопрос я не смог, так как не был зарегистрирован, регистрация возможна для новых пользователей только на новом форуме. Но здесь я не нашел данную тему. Если правила не запрещают, то я могу выложить ссылку на тему старого форума.
С работой кода многоуровневого меню почти разобрался. Многое понял, как изменить и модифицировать. Но возник вопрос, который я не могу задать на старом форуме. Задам здесь.
Сам код выводит все пункты меню. Предположим, что их 20, а на экран помещается только 8. Подскажите, пожалуйста, как выводить на экран заданное число строк меню (к примеру 4 или 8), и листать строки меню вверх, при переходе на следующий пункт меню, “не помещающийся на экране”?
Вот код меню, автором которого является ЕвгенийП.
int id; // уникальный идентификатор данного элемента
SMenuItem * prev; // следующий элемент данного урвоня (nullptr - если этот - последний)
SMenuItem * next; // предыдущий элемент данного урвоня (nullptr - если этот - первый)
SMenuItem * parent; // адрес родительского элемента (nullptr - если нет родителей)
SMenuItem * children; // адрес списка элементов "подменю" (nullptrll - если это лист)
const char * itemText; // Название данного элемента (тут может быть не текст, а адрес картинки. Функция show знает, что с этим делать)
//
// Показать данный элемент
void show(void) {
// ДОПОЛНЕНИЕ - если мы активны, печатаем звёздочку, иначе - пробел
Serial.print(iAmActive ? '*' : ' ');
for (SMenuItem * ptr = parent; ptr; ptr = ptr->parent) {
if (ptr->parent) Serial.print(" ");
}
Serial.println(itemText);
}
//
// Показать всех детей данного элемента
// Параметр: глубина показа "внуков".
// Если 1 - только дети, если 2 - то и внуик и т.д.
void showChildren(const int showGrandChilren = 1) {
if (! showGrandChilren) return;
for (SMenuItem * ptr = children; ptr; ptr = ptr->next) {
ptr->show();
ptr->showChildren(showGrandChilren - 1);
}
}
//
// выполнить действие, когда этот элемент выбран
void action(void) {
Serial.println("***********************************************");
Serial.print("***** The menu \"");
Serial.print(itemText);
Serial.println("\" is executed");
Serial.println("***********************************************");
}
//
// Удаление данного элемент из меню
void removeMe(void) {
//
// Если есть следующий элемент, то
// делаем мой предыдущий, его предыдущим
if (next) next->prev = prev;
//
// Если есть предыдущий
// то делаем наш следущий, его следущим
// Иначе говорим родителю, что наше next - его первый ребёнок.
if (prev) prev->next = next;
else parent->children = next;
}
//
// Вставить себя как первого ребёнка объекта _parent
void insertAsFirstChild(SMenuItem & _parent) {
//
// Вставляем первого ребёнка после себя, а себя делаем первым ребёнком
next = _parent.children;
prev = nullptr;
_parent.children = this;
}
//
// Вставить себя после объекта _prev
void insertAfter(SMenuItem & _prev) {
//
// Ставим себя следующим объекту _prev, а его следующего - своим следующим
next = _prev.next;
_prev.next = this;
}
//
// Конструктор - создать элемент
SMenuItem(const char * const _itemText, SMenuItem * _prev = nullptr, SMenuItem * _parent = nullptr) {
itemText = _itemText;
prev = _prev;
parent = _parent;
children = nullptr;
next = nullptr;
//
// Если у нас есть родитель
if (parent) {
// если у родителя пока нет детей, записываемся началом списка детей.
if (parent->children == nullptr) parent->children = this;
}
//
// Если у нас есть предыдущий элемент
if (prev) {
// записываемся ему в "следующие"
prev->next = this;
}
//
// ДОПОЛНЕНИЕ - мы неактивны
iAmActive = false;
}
//
// ДОПОЛНЕНИЯ
//
bool iAmActive;
// Активировать (параметр - true) или деактивировать
// Возвращает адрес себя
SMenuItem * activate(const bool actDeact) {
iAmActive = actDeact;
return this;
}
// Перейти к следующему (если есть)
// Возвращает адрес нового (или старого, если не перешли) активного элемента
SMenuItem * goNext(void) {
// Если есть следующий, то активируем его
if (next) {
activate(false); // деактивируем себя
return next->activate(true);
}
return this;
}
// Перейти к предыдущему (если есть)
// Возвращает адрес нового (или старого, если не перешли) активного элемента
SMenuItem * goPrev(void) {
// Если есть предыдущий, то активируем его
if (prev) {
activate(false); // деактивируем себя
return prev->activate(true);
}
return this;
}
// Перейти к родителю (если есть)
// на самом деле переходим только в том случае, если у родителя есть
// ещё и свой родитель, т.к. нам не нужно переходиь к общему прародителю
// (в нашем случае к m0)
// Возвращает адрес нового (или старого, если не перешли) активного элемента
SMenuItem * goParent(void) {
// Если есть родитель и у того тоже есть родитель, то активируем родителя
if (parent && parent->parent) {
activate(false); // деактивируем себя
return parent->activate(true);
}
return this;
}
// Если нет детей, то выполнить данный пункт
// А если есть дети, то перейти к первому ребёнку
// Возвращает адрес нового (или старого, если не перешли) активного элемента
SMenuItem * goChildOrRun(void) {
// Если нет детей, то выполняем данный элемент
if (! children) action();
else {
activate(false); // деактивируем себя
return children->activate(true);
}
return this;
}
};
// Корневой элемент всего меню. Он обычно невидимый.
// он родитель элементов верхнего уровня!
SMenuItem m0(""); // у этого нет ни предыдущего, ни родителя
SMenuItem m1("Menu1", nullptr, & m0); // у этого нет ни предыдущего
SMenuItem m2("Menu2", & m1, &m0);
SMenuItem m3("Menu3", & m2, &m0);
SMenuItem m4("Menu4", & m3, &m0);
SMenuItem m5("Menu5", & m4, &m0);
SMenuItem m6("Menu6", & m5, &m0);
SMenuItem m11("Menu11", nullptr, & m1);// у этого нет предыдущего, но есть родитель
SMenuItem m12("Menu12", & m11, & m1); // у этого есть и предыдущий, и родитель
SMenuItem m41("Menu41", nullptr, & m4);// у этого нет предыдущего, но есть родитель
SMenuItem m42("Menu42", & m41, & m4); // у этого есть и предыдущий, и родитель
SMenuItem m411("Menu411", nullptr, & m41);
SMenuItem m412("Menu412", & m411, & m41);
SMenuItem m413("Menu413", & m412, & m41);
//
// Ввод числа от 1 до 4
//
int prompt(void) {
int res = 0;
while (res < 1 || res > 4) {
Serial.println ("Введи число от 1 до 4: ");
Serial.println (" 1 - к предыдущему;");
Serial.println (" 2 - к следующему;");
Serial.println (" 3 - к родителю;");
Serial.println (" 4 - к ребёнку или (если нет детей, то выполнить)");
res = Serial.parseInt(); // Вводим число
while(Serial.available()) Serial.read(); // Вычитываем всё, что осталось
}
return res;
}
void setup() {
Serial.begin(115200);
Serial.setTimeout(0xFFFFFFFF);
Serial.println("Веселье начинается!");
}
void loop() {
static SMenuItem * activeElement = m1.activate(true);
Serial.println("----------------------------------");
m0.showChildren(1000); // Печатаем меню
// Обрабатываем комнду
// 1 - продвинуться по меню вверх, если есть куда (на том же уровне)
// 2 - продвинуться вниз, если есть куда (на том же уровне)
// 3 - перейти на уровень вверх (к родителю)
// 4 - если это не листик, то перейти на уровень вниз, а если листик, то выполнить этот пункт
switch (prompt()) {
case 1: activeElement = activeElement->goPrev(); break;
case 2: activeElement = activeElement->goNext(); break;
case 3: activeElement = activeElement->goParent(); break;
case 4: activeElement = activeElement->goChildOrRun(); break;
}
}```
Как я понял, за вывод строк меню отвечает метод showChildren, который печатает все строки меню вызывая метод show(), но как заставить showChildren печатать не все, а нужное число строк на экран с возможностью их скролинга?