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

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

318 lines
16 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file menu.h
* @brief Портируемое меню для дисплея и шести кнопок навигации.
*
* Движок хранит стек открытых экранов, курсор и окно прокрутки, а содержимое
* запрашивает у приложения обратными вызовами: так один и тот же экран описывает
* и статический список, и перечень датчиков, число которых меняется на ходу.
*
* Рисует движок через таблицу Menu_Painter, поэтому от драйвера дисплея он не
* зависит: для ST7789V адаптер занимает несколько строк, для знакосинтезирующего
* ЖКИ или SSD1306 — столько же.
*/
#ifndef MENU_H
#define MENU_H
#include <stdint.h>
/** Предельная глубина вложенности экранов. */
#define MENU_MAX_DEPTH 4U
/** Размер буфера строки пункта вместе с завершающим нулём. */
#define MENU_TEXT_MAX 32U
/** Предельное число одновременно видимых пунктов; ограничивает кэш строк. */
#define MENU_MAX_ROWS 20U
/** @brief Кнопки, которые понимает движок меню. */
typedef enum {
MENU_KEY_UP = 0, /**< Предыдущий пункт. */
MENU_KEY_DOWN, /**< Следующий пункт. */
MENU_KEY_LEFT, /**< Уменьшить значение пункта. */
MENU_KEY_RIGHT, /**< Увеличить значение пункта. */
MENU_KEY_ENTER, /**< Войти в пункт или подтвердить. */
MENU_KEY_BACK /**< Вернуться на предыдущий экран. */
} Menu_Key;
/** @brief Экран меню; описание неизменно и хранится во флеш-памяти. */
typedef struct Menu_Screen Menu_Screen;
/**
* @brief Сообщает текущее число пунктов экрана.
*
* @param context Контекст экрана либо меню.
* @return Число пунктов; ноль допустим.
*/
typedef uint8_t (*Menu_CountFn)(void *context);
/**
* @brief Формирует текст пункта или его значения.
*
* @param context Контекст экрана либо меню.
* @param index Номер пункта.
* @param out Буфер приёмника, завершается нулём.
* @param size Размер буфера в байтах.
*/
typedef void (*Menu_TextFn)(void *context, uint8_t index, char *out, uint8_t size);
/**
* @brief Обрабатывает нажатие ввода на пункте.
*
* @param context Контекст экрана либо меню.
* @param index Номер пункта.
* @return Экран для открытия либо 0, если пункт лишь выполняет действие.
*/
typedef const Menu_Screen *(*Menu_EnterFn)(void *context, uint8_t index);
/**
* @brief Меняет значение пункта кнопками влево и вправо.
*
* @param context Контекст экрана либо меню.
* @param index Номер пункта.
* @param delta Шаг: -1 для влево, +1 для вправо.
*/
typedef void (*Menu_AdjustFn)(void *context, uint8_t index, int8_t delta);
/**
* @brief Описание экрана меню.
*
* Обязательны заголовок и обратный вызов @c label. Число пунктов берётся из
* @c count, а при нулевом указателе — из поля @c item_count.
*/
struct Menu_Screen {
const char *title; /**< Заголовок в верхней полосе. */
uint8_t item_count; /**< Число пунктов статического экрана. */
Menu_CountFn count; /**< Число пунктов динамического экрана либо 0. */
Menu_TextFn label; /**< Текст пункта; обязателен. */
Menu_TextFn value; /**< Текст значения справа либо 0. */
Menu_EnterFn enter; /**< Реакция на ввод либо 0. */
Menu_AdjustFn adjust; /**< Реакция на влево и вправо либо 0. */
void *context; /**< Контекст экрана; 0 — использовать контекст меню. */
};
/**
* @brief Обратные вызовы отрисовки и метрика шрифта.
*
* Цвета передаются как есть, поэтому в них укладывается и RGB565, и палитра
* монохромного индикатора.
*/
typedef struct {
/** Заливает прямоугольник цветом. */
void (*fill_rect)(void *context, int16_t x, int16_t y, uint16_t width,
uint16_t height, uint32_t color);
/** Выводит строку с непрозрачным фоном заданным масштабом шрифта. */
void (*draw_text)(void *context, int16_t x, int16_t y, const char *text,
uint32_t color, uint32_t background, uint8_t scale);
void *context; /**< Указатель адаптера дисплея. */
uint16_t width; /**< Ширина области вывода, пикселей. */
uint16_t height; /**< Высота области вывода, пикселей. */
uint8_t char_width; /**< Шаг знакоместа при масштабе 1. */
uint8_t char_height; /**< Высота знакоместа при масштабе 1. */
} Menu_Painter;
/** @brief Цвета и отступы оформления. */
typedef struct {
uint32_t background; /**< Фон рабочей области. */
uint32_t title_fg; /**< Текст заголовка. */
uint32_t title_bg; /**< Фон заголовка. */
uint32_t item_fg; /**< Текст обычного пункта. */
uint32_t item_bg; /**< Фон обычного пункта. */
uint32_t value_fg; /**< Текст значения обычного пункта. */
uint32_t cursor_fg; /**< Текст выбранного пункта. */
uint32_t cursor_bg; /**< Фон выбранного пункта. */
uint32_t status_fg; /**< Текст нижней строки состояния. */
uint32_t status_bg; /**< Фон нижней строки состояния. */
uint32_t scroll_fg; /**< Указатели прокрутки. */
uint8_t title_scale; /**< Масштаб шрифта заголовка. */
uint8_t item_scale; /**< Масштаб шрифта пунктов. */
uint8_t padding; /**< Отступ от краёв, пикселей. */
uint8_t row_gap; /**< Просвет между пунктами, пикселей. */
} Menu_Theme;
/** @brief Состояние меню; принадлежит вызывающему коду. */
typedef struct {
const Menu_Screen *stack[MENU_MAX_DEPTH]; /**< Стек открытых экранов. */
uint8_t cursor[MENU_MAX_DEPTH]; /**< Выбранный пункт по уровням. */
uint8_t first[MENU_MAX_DEPTH]; /**< Верхний видимый пункт. */
uint8_t depth; /**< Число экранов в стеке. */
Menu_Painter painter; /**< Обратные вызовы отрисовки. */
Menu_Theme theme; /**< Оформление. */
void *context; /**< Контекст приложения. */
uint8_t rows; /**< Сколько пунктов видно сразу. */
uint8_t dirty; /**< 1 — экран требует перерисовки. */
char status[MENU_TEXT_MAX]; /**< Нижняя строка состояния. */
/*
* Кэш последней отрисовки. Перерисовываются только изменившиеся строки,
* поэтому обновление данных не вызывает мигания всего экрана.
*/
const Menu_Screen *cache_screen; /**< Экран, для которого кэш верен. */
uint8_t cache_first; /**< Верхний видимый пункт в кэше. */
uint8_t cache_valid; /**< 1 — кэш содержит отрисованное. */
uint8_t cache_selected[MENU_MAX_ROWS]; /**< Признак курсора по строкам. */
char cache_label[MENU_MAX_ROWS][MENU_TEXT_MAX]; /**< Названия строк. */
char cache_value[MENU_MAX_ROWS][MENU_TEXT_MAX]; /**< Значения строк. */
char cache_title[MENU_TEXT_MAX]; /**< Заголовок экрана. */
char cache_status[MENU_TEXT_MAX]; /**< Строка состояния с указателями. */
} Menu;
/**
* @brief Снимок видимой части экрана.
*
* Повторяет то, что сейчас нарисовано на панели: движок отдаёт содержимое из
* кэша последней отрисовки, не обращаясь к обратным вызовам приложения.
* Поэтому снимок дёшев и совпадает с изображением байт в байт.
*
* Строки указывают внутрь состояния меню и действительны до следующего
* вызова Menu_Render.
*/
typedef struct {
const char *title; /**< Заголовок верхней полосы. */
const char *status; /**< Нижняя строка вместе с указателями прокрутки. */
const char *label[MENU_MAX_ROWS]; /**< Названия видимых пунктов. */
const char *value[MENU_MAX_ROWS]; /**< Значения видимых пунктов; пустая строка, если нет. */
uint8_t selected[MENU_MAX_ROWS]; /**< 1 — на этой строке курсор. */
uint8_t rows; /**< Число заполненных строк снимка. */
uint8_t first; /**< Номер верхнего видимого пункта. */
uint8_t total; /**< Всего пунктов на открытом экране. */
uint8_t cursor; /**< Выбранный пункт, номер в пределах экрана. */
uint8_t depth; /**< Глубина стека экранов; 1 — корневой. */
} Menu_Snapshot;
/**
* @brief Заполняет оформление тёмной темой для цветного дисплея.
*
* Значения цветов задаются в формате RGB565, отступы рассчитаны на шрифт 6x8.
*
* @param theme Оформление, принадлежащее вызывающему коду.
*/
void Menu_ThemeDefault(Menu_Theme *theme);
/**
* @brief Готовит меню к работе и открывает корневой экран.
*
* @param menu Состояние меню.
* @param painter Обратные вызовы отрисовки; копируются внутрь состояния.
* @param theme Оформление; при значении 0 берётся Menu_ThemeDefault().
* @param root Корневой экран.
* @param context Контекст приложения для экранов без своего контекста.
* @return 1 при успешной настройке, 0 при неполных аргументах.
*/
uint8_t Menu_Init(Menu *menu, const Menu_Painter *painter, const Menu_Theme *theme,
const Menu_Screen *root, void *context);
/**
* @brief Обрабатывает нажатие кнопки навигации.
*
* Перерисовка не выполняется: функция только меняет состояние и поднимает
* признак устаревшего изображения.
*
* @param menu Состояние меню.
* @param key Нажатая кнопка.
*/
void Menu_HandleKey(Menu *menu, Menu_Key key);
/**
* @brief Открывает вложенный экран, если стек не заполнен.
*
* @param menu Состояние меню.
* @param screen Открываемый экран.
*/
void Menu_Open(Menu *menu, const Menu_Screen *screen);
/**
* @brief Возвращается на предыдущий экран; на корневом ничего не делает.
*
* @param menu Состояние меню.
*/
void Menu_Back(Menu *menu);
/**
* @brief Закрывает все вложенные экраны и возвращается к корневому.
*
* @param menu Состояние меню.
*/
void Menu_Home(Menu *menu);
/**
* @brief Помечает изображение устаревшим после обновления данных.
*
* @param menu Состояние меню.
*/
void Menu_Invalidate(Menu *menu);
/**
* @brief Сообщает, требуется ли перерисовка.
*
* @param menu Состояние меню.
* @return 1, если изображение устарело.
*/
uint8_t Menu_IsDirty(const Menu *menu);
/**
* @brief Задаёт текст нижней строки состояния.
*
* @param menu Состояние меню.
* @param text Строка либо 0, чтобы очистить строку.
*/
void Menu_SetStatus(Menu *menu, const char *text);
/**
* @brief Перерисовывает экран, если изображение устарело.
*
* @param menu Состояние меню.
* @param force 1 — рисовать безусловно.
*/
void Menu_Render(Menu *menu, uint8_t force);
/**
* @brief Возвращает открытый экран.
*
* @param menu Состояние меню.
* @return Текущий экран либо 0, если меню не настроено.
*/
const Menu_Screen *Menu_Current(const Menu *menu);
/**
* @brief Возвращает номер выбранного пункта открытого экрана.
*
* @param menu Состояние меню.
* @return Номер пункта; 0 для пустого экрана.
*/
uint8_t Menu_Cursor(const Menu *menu);
/**
* @brief Снимает видимое содержимое экрана для передачи наружу.
*
* Источник данных — кэш последней отрисовки, поэтому вызывать функцию имеет
* смысл после Menu_Render: до первой отрисовки кэш пуст и снимок не выдаётся.
*
* @param menu Состояние меню.
* @param out Снимок, принадлежащий вызывающему коду.
* @return 1, если снимок заполнен, 0 при пустом кэше или неверных аргументах.
*/
uint8_t Menu_GetSnapshot(const Menu *menu, Menu_Snapshot *out);
/**
* @brief Копирует строку в буфер пункта с обрезкой по размеру.
*
* Вспомогательная функция для обратных вызовов приложения.
*
* @param out Буфер приёмника.
* @param size Размер буфера вместе с завершающим нулём.
* @param text Копируемая строка либо 0.
*/
void Menu_TextCopy(char *out, uint8_t size, const char *text);
/**
* @brief Печатает целое со знаком без обращения к stdio.
*
* @param out Буфер приёмника.
* @param size Размер буфера вместе с завершающим нулём.
* @param value Печатаемое значение.
* @param suffix Приписываемая справа строка либо 0.
*/
void Menu_TextInt(char *out, uint8_t size, int32_t value, const char *suffix);
#endif /* MENU_H */