Перенесён из KONOR_ds18b20/lib/menu; в OpticalTester лежала такая же копия. Содержимое экрана движок запрашивает обратными вызовами, поэтому один экран описывает и статический список, и перечень датчиков переменной длины. Рисует через Menu_Painter: от драйвера дисплея не зависит, цвет передаётся как есть — подходит и RGB565, и монохром.
318 lines
16 KiB
C
318 lines
16 KiB
C
/**
|
||
* @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 */
|