Files
Andrey Kruchinkin 1248a6551a feat(menu): экранное меню со стеком экранов и прокруткой
Перенесён из KONOR_ds18b20/lib/menu; в OpticalTester лежала такая же копия.

Содержимое экрана движок запрашивает обратными вызовами, поэтому один
экран описывает и статический список, и перечень датчиков переменной
длины. Рисует через Menu_Painter: от драйвера дисплея не зависит,
цвет передаётся как есть — подходит и RGB565, и монохром.
2026-08-23 01:15:12 +03:00
..

menu

Экранное меню для дисплея и шести кнопок навигации.

Движок хранит стек открытых экранов, курсор и окно прокрутки, а содержимое запрашивает у приложения обратными вызовами: так один и тот же экран описывает и статический список, и перечень датчиков, число которых меняется на ходу.

Рисует движок через таблицу Menu_Painter, поэтому от драйвера дисплея он не зависит: для ST7789V адаптер занимает несколько строк, для знакосинтезирующего ЖКИ или SSD1306 — столько же.

   приложение
     count/text/enter/adjust        ← чем наполнен экран
             │
          menu.c        стек экранов, курсор, прокрутка, перерисовка
             │
      Menu_Painter      fill_rect, draw_text
             │
      драйвер дисплея

Состав

Файл Что делает Зависимости
menu.h, menu.c стек экранов до 4 уровней, курсор, прокрутка, тема оформления, строка состояния stdint.h

Вспомогательное: Menu_TextCopy() и Menu_TextInt() — форматирование строк пунктов без snprintf. Menu_GetSnapshot() отдаёт состояние без отрисовки, что удобно для хостовых тестов.

Что нужно от платформы

Только два вызова отрисовки — цвет передаётся как есть, поэтому в него укладывается и RGB565, и палитра монохромного индикатора:

void fill_rect(void *ctx, int16_t x, int16_t y, uint16_t w, uint16_t h, uint32_t color);
void draw_text(void *ctx, int16_t x, int16_t y, const char *text,
               uint32_t color, uint32_t background, uint8_t scale);

Быстрый старт

static const Menu_Screen screen_root = {
    .title = "Главное меню",
    .count = root_count,      /* сколько пунктов сейчас */
    .text  = root_text,       /* текст пункта по индексу */
    .enter = root_enter,      /* вернуть вложенный экран или 0 */
    .context = &app,
};

Menu menu;
Menu_Theme theme;
Menu_ThemeDefault(&theme);
Menu_Init(&menu, &painter, &theme, &screen_root);

Menu_HandleKey(&menu, MENU_KEY_DOWN);
Menu_Render(&menu, 0);        /* перерисует только если что-то изменилось */

Данные, меняющиеся сами по себе (температура, состояние связи), обновляются вызовом Menu_Invalidate() — движок перерисует экран на ближайшем Menu_Render().

Проверено в проектах

KONOR_ds18b20, OpticalTester — поверх st7789 и keypad. Меню от них не зависит: связывает всё адаптер в приложении.