Статус-бар, виджеты, списки, плитки и графики.
Эта страница повторяет компоненты и готовые UI-паттерны из API.md: status bar, widgets, list/tile menu и graph subsystem.
12. Статус-бар
12.1. Build-time флаги
PIPGUI_STATUS_BAR1включает код статус-бара0вырезает runtime-реализацию, публичные методы остаются no-opPIPGUI_DEBUG_METRICS1включает debug-режим: библиотека рисует диагностический текст в статус-баре (FPS/время кадра/память и т.п.)0выключено (по умолчанию)
Обычно эти флаги задаются в include/config.hpp.
12.2. Включение
ui.configStatusBar()
.height(18)
.pos(Top)
.style(Blur);
Позиции:
TopBottom
Стили:
Solid— обычная непрозрачная полоса; layout резервирует под неё высотуBlur— блюр-полоса поверх контента; layout не должен откусывать под неё safe area
12.3. Текст
ui.setStatusBarText()
.left("PipGUI")
.center("12:34")
.right("Wi-Fi");
12.4. Батарея
ui.setStatusBarBattery(87, Numeric);
ui.setStatusBarBattery(100, Bar);
ui.setStatusBarBattery(-1, Hidden);
BatteryStyle:
HiddenNumericBarWarningBarErrorBar
12.5. Кастомная дорисовка
void statusBarCustom(GUI &ui, int16_t x, int16_t y, int16_t w, int16_t h)
{
ui.drawLine()
.from(x, y + h - 1)
.to(x + w, y + h - 1)
.color(ui.rgb(70, 70, 70))
.draw();
}
ui.setStatusBarCustom(statusBarCustom);
Вспомогательные методы:
int16_t h = ui.statusBarHeight();
ui.updateStatusBar();
ui.renderStatusBar();
Что важно:
statusBarHeight()возвращает ненулевую высоту только дляSolid- при
Blurhelper возвращает0, потому что layout не должен резервировать fixed-height safe area под blur-панель
12.6. Иконки слотов
Можно повесить отдельную иконку в левый, центральный или правый слот статус-бара:
ui.setStatusBarIcon()
.side(Left)
.icon(warning);
ui.setStatusBarIcon()
.side(Center)
.icon(error)
.color(ui.rgb(255, 80, 80))
.size(16);
ui.setStatusBarIcon()
.side(Right)
.icon(warning)
.color(ui.rgb(255, 220, 120))
.size(14);
Параметры:
- первый аргумент - слот:
Left,CenterилиRight - второй аргумент -
IconId colorнеобязателен; если не задан, берётся foreground-цвет статус-бараsizePxнеобязателен; если0, размер подбирается автоматически от высоты панели
Удаление иконки:
ui.clearStatusBarIcon(Left);
ui.clearStatusBarIcon(Center);
ui.clearStatusBarIcon(Right);
Поведение:
- иконки появляются и исчезают с короткой fade-анимацией
- левая иконка живёт в одном block-е с левым текстом
- центральная иконка центрируется вместе с центральным текстом как одна группа
- правая иконка живёт в правом block-е вместе с правым текстом
13. Виджеты
13.1. Scroll dots
ui.drawScrollDots()
.pos(center, 220)
.count(5)
.activeIndex(2)
.activeColor(ui.rgb(0, 87, 250))
.inactiveColor(ui.rgb(60, 60, 60))
.radius(3)
.spacing(14);
Есть и updateScrollDots() с теми же параметрами.
Что задают параметры:
count(...)- общее число страниц/точекactiveIndex(...)- текущая активная страницаradius(...)- базовый радиус точкиspacing(...)- шаг между центрами соседних точек
Поведение:
- при
count > 7включается оконный режим: показывается компактное окно точек с taper по краям
13.2. Buttons
Обычная отрисовка:
ui.drawButton()
.label("Save") // текст внутри кнопки
.pos(center, 180) // центр по X, координата Y
.size(120, 40) // ширина и высота кнопки
.baseColor(ui.rgb(0, 120, 255)) // основной цвет кнопки
.radius(10); // радиус
Обновление с состоянием кнопки:
ui.updateButton()
.label("Save") // текст внутри кнопки
.pos(center, 180) // центр по X, координата Y
.size(120, 40) // ширина и высота кнопки
.baseColor(ui.rgb(0, 120, 255)) // основной цвет кнопки
.radius(10) // радиус
.icon(warning) // иконка внутри кнопки
.mode(true, false) // enabled, loading
.down(isDown) // текущее физическое нажатие для press-анимации
;
Кнопка с прогрессом:
ui.updateButton()
.label("Updating 56%") // текст внутри кнопки
.pos(center, 180) // центр по X, координата Y
.size(170, 38) // ширина и высота кнопки
.baseColor(ui.rgb(24, 24, 24)) // базовый цвет корпуса кнопки
.fillColor(ui.rgb(0, 120, 255)) // цвет progress-заливки
.value(56) // значение встроенного progress: 0..100
.radius(12) // радиус
.icon(battery_l1); // иконка внутри кнопки
drawButton() и updateButton() используют один и тот же API. Для обычных статичных экранов достаточно drawButton(). Для анимируемой или интерактивной кнопки используй updateButton().
Если заданы и текст, и иконка - иконка рисуется слева от текста как единый центрированный блок. Если текст пустой, иконка рисуется по центру кнопки. Если задан value(...), кнопка рисует встроенный progress-fill под текстом и иконкой. loading и progress одновременно не используются: progress-режим приоритетнее.
13.3. Toggle switch
Снаружи нужен только обычный bool:
bool wifiEnabled = false;
bool changed = false;
Отрисовка:
ui.updateToggleSwitch()
.pos(center, 140)
.size(78, 36)
.value(wifiEnabled) // текущий bool; библиотека сама обновит его при нажатии
.pressed(btn.wasPressed()) // событие нажатия этого кадра
.changed(changed) // сюда вернется true, если значение переключилось
.enabled(!wifiBusy) // можно временно отключить ввод, пока идет внешняя операция
.activeColor(ui.rgb(21, 180, 110)) // цвет включенного track
.inactiveColor(ui.rgb(46, 46, 46)) // цвет выключенного track
.knobColor(0xFFFF); // цвет бегунка
drawToggleSwitch() и updateToggleSwitch() используют один и тот же fluent API. Разница только в режиме вывода:
drawToggleSwitch()- обычная отрисовкаupdateToggleSwitch()- локальный dirty-update
Если хочется просто показать состояние без ввода, можно не передавать pressed(...) и changed(...):
ui.drawToggleSwitch()
.pos(center, 140)
.size(78, 36)
.value(wifiEnabled)
.activeColor(ui.rgb(21, 180, 110));
13.4. Slider
Слайдер подходит для настроек вроде громкости, яркости и подобных значений.
ui.updateSlider()
.pos(center, 114) // центр по X, координата Y
.size(186, 24) // ширина и высота трека
.bind(value) // привязка переменной; библиотека обновляет ее сама
.activeColor(ui.rgb(0, 87, 250)) // цвет заполненной части
.inactiveColor(ui.rgb(36, 36, 36)) // цвет неактивной части трека
// .enabled(false) // опционально: показать slider без реакции на ввод
.thumbColor(0xFFFF); // цвет бегунка
drawSlider() и updateSlider() используют один и тот же fluent API. Для интерактивного сценария нужен updateSlider(). Ввод Next/Prev slider берет сам из последнего pollInput(...).
Поведение:
- удержание кнопки сначала двигает значение обычным шагом, потом ускоряет и частоту, и величину шага;
- по умолчанию бегунок белый, трек темнее активной части.
13.5. Прогресс
ui.drawProgress()
.pos(20, 220) // левый верхний угол
.size(180, 16) // ширина и высота
.value(65) // значение progress: 0..100
.baseColor(ui.rgb(30, 30, 30)) // цвет пустой части
.fillColor(ui.rgb(0, 120, 255)) // цвет заполненной части
.radius(8) // скругление краев
.anim(Shimmer); // тип анимации progress
Для локального обновления без полной перерисовки есть updateProgress():
ui.updateProgress()
.pos(20, 220) // та же область, которую нужно обновить
.size(180, 16) // тот же размер
.value(65) // новое значение: 0..100
.baseColor(ui.rgb(30, 30, 30)) // цвет пустой части
.fillColor(ui.rgb(0, 120, 255)) // цвет заполненной части
.radius(8) // скругление краев
.anim(Shimmer); // тип анимации progress
Текст у линейного прогресса задаётся прямо на самом progress:
ui.drawProgress()
.pos(20, 246) // левый верхний угол
.size(180, 14) // ширина и высота
.value(65) // значение progress: 0..100
.baseColor(ui.rgb(20, 20, 20)) // цвет пустой части
.fillColor(ui.rgb(0, 120, 255)) // цвет заполненной части
.label("Downloading", Left) // текст и его выравнивание
.labelColor(ui.rgb(255, 255, 255)) // цвет label
.percent(Right) // показать текущее значение как процент и задать выравнивание
.percentColor(ui.rgb(200, 200, 200)); // цвет процента
Текст поддерживается только у линейного progress; у drawCircleProgress() его нет.
Для label(...) и percent(...) доступны:
Left- прижать текст к левому краю progressCenter- выровнять текст по центру progressRight- прижать текст к правому краю progress
anim(...) у progress поддерживает:
None- progress статичный, без анимацииShimmer- по заполненной части идет мягкий движущийся блик
13.6. Круговой прогресс
ui.drawCircleProgress()
.pos(center, 140) // центр кольца
.radius(34) // внешний радиус
.thickness(8) // толщина кольца
.value(72) // значение progress: 0..100
.baseColor(ui.rgb(30, 30, 30)) // цвет пустой части
.fillColor(ui.rgb(0, 120, 255)) // цвет заполненной части
.anim(None); // тип анимации progress
Локальное обновление:
ui.updateCircleProgress()
.pos(center, 140) // тот же центр
.radius(34) // тот же внешний радиус
.thickness(8) // та же толщина кольца
.value(72) // новое значение: 0..100
.baseColor(ui.rgb(30, 30, 30)) // цвет пустой части
.fillColor(ui.rgb(0, 120, 255)) // цвет заполненной части
.anim(None); // тип анимации progress
13.7. Drum roll
Горизонтальный:
ui.drawDrumRoll()
.pos(20, 60) // левый верхний угол
.size(200, 40) // ширина и высота области
.options(16, "Low", "Medium", "High") // шрифт и список опций; количество считается автоматически
.selected(1) // текущий выбранный индекс
.fgColor(ui.rgb(255, 255, 255)) // цвет текста
.bgColor(ui.rgb(0, 0, 0)); // цвет фона
Вертикальный:
ui.drawDrumRoll()
.pos(220, 100) // левый верхний угол
.size(70, 90) // ширина и высота области
.options(16, "Low", "Medium", "High") // шрифт и список опций; количество считается автоматически
.selected(1) // текущий выбранный индекс
.fgColor(ui.rgb(255, 255, 255)) // цвет текста
.bgColor(ui.rgb(0, 0, 0)) // цвет фона
.vertical(); // включить вертикальный режим
14. Списки и плитки
14.1. Списочное меню
Прямо в SCREEN(...):
SCREEN(ScreenMainMenu, 1)
{
ui.clear(0x0000);
ui.updateList()
.items(
listItem("Settings", "Device configuration", ScreenSettings),
listItem("About", "Firmware info", ScreenAbout),
listItem("Restart", "Reboot device", ScreenRestart))
.inactive(ui.rgb(12, 12, 12)) // цвет обычной карточки
.active(ui.rgb(0, 120, 255)) // цвет активной карточки
// .checked(1) // опционально: справа рисовать галочку у этого пункта
.radius(8) // радиус карточек
.cardSize(0, 0) // 0,0 = размер подберётся автоматически
.mode(Cards); // режим списка
}
Режимы списка:
CardsPlain
Поведение:
- короткое отпускание
NEXTпереключает пункт вперёд; - короткое отпускание
PREVпереключает пункт назад; - 2-button режим: удержание
NEXTоткрываетtargetScreenвыбранного пункта; - 3-button режим: короткое нажатие
SELECTоткрываетtargetScreenвыбранного пункта; - удержание
PREVвозвращает на предыдущий экран из navigation-history.
14.2. Плиточное меню
Прямо в SCREEN(...):
SCREEN(ScreenTiles, 2)
{
ui.updateTile()
.items(
tileItem("Main", "Главный экран", ScreenHome),
tileItem("Settings", "Настройки", ScreenSettings),
tileItem("Info", "Инфо", ScreenInfo),
tileItem("Graph", "Графики", ScreenGraph))
.inactive(ui.rgb(16, 16, 16)) // обычная плитка
.active(ui.rgb(0, 120, 255)) // активная плитка
.radius(12) // радиус плитки
.spacing(10) // расстояние между плитками
.columns(2) // количество колонок в обычной сетке
.tileSize(100, 70) // желаемый размер плитки
.mode(TextSubtitle); // контент плитки
}
Поведение:
- короткое отпускание
NEXTпереключает плитку вперёд; - короткое отпускание
PREVпереключает плитку назад; - 2-button режим: удержание
NEXTоткрываетtargetScreenвыбранной плитки; - 3-button режим: короткое нажатие
SELECTоткрываетtargetScreenвыбранной плитки; - удержание
PREVвозвращает на предыдущий экран из navigation-history.
Режимы плитки:
TextOnlyTextSubtitle
14.3. Кастомная раскладка плиток
Если обычной сетки мало, можно задать свою понятную раскладку:
ui.updateTile()
.grid(2, 3) // сетка: 2 колонки, 3 строки
.tile("Main", "Главный экран", ScreenHome)
.at(0, 0) // колонка 0, строка 0
.span(2, 1) // ширина 2 клетки, высота 1 клетка
.tile("Settings", "Настройки", ScreenSettings)
.at(0, 1) // колонка 0, строка 1
.tile("Info", "Инфо", ScreenInfo)
.at(1, 1) // колонка 1, строка 1
.tile("Graph", "Графики", ScreenGraph)
.at(1, 2) // колонка 1, строка 2
Что важно:
grid(cols, rows)включает кастомную сеткуtile(...)добавляет очередную плиткуat(col, row)ставит последнюю добавленную плитку в нужную клеткуspan(cols, rows)растягивает последнюю добавленную плитку на несколько клеток- в этом режиме
columns(...)уже не влияет на раскладку
15. Графики
15.1. Фон и сетка:
ui.drawGraphGrid()
.pos(10, 50)
.size(220, 120)
.radius(8)
.direction(LeftToRight) // задаёт режим движения и раскладки данных внутри этой graph-area
.bgColor(ui.rgb(10, 10, 10)) // цвет сетки вычисляется автоматически из bgColor()
.speed(1.0f);
drawGraphGrid()/updateGraphGrid()должны использовать тот жеdirection(...), в котором потом рисуются линии этого графа
Направления:
LeftToRight- новые точки приходят справа, старая история уезжает влевоRightToLeft- новые точки приходят слева, старая история уезжает вправоOscilloscope- фиксированное окно по всей ширине графика без rolling-shift; точки раскладываются по видимому буферу как осциллограф
Для локального dirty-redraw доступен тот же fluent через updateGraphGrid():
ui.updateGraphGrid()
.pos(10, 50)
.size(220, 120)
.radius(8)
.direction(LeftToRight)
.bgColor(ui.rgb(10, 10, 10))
.speed(1.0f);
15.2. Линия графика:
ui.drawGraphLine()
.line(0)
.value(sensorValue)
.thickness(2) // задаёт толщину линии графика; по умолчанию `1`
.color(ui.rgb(0, 255, 140))
.range(0, 100);
.scale(true); // вкл/выкл автоматический диапазон по данным для этой graph-area
drawGraphLine()добавляет новую точку в уже настроенный графикupdateGraphLine()подходит для in-place обновления, когда графику нужно самому зачистить и перерисовать нужную область
15.3. Пакетная отрисовка готового массива:
int16_t samples[] = {10, 15, 12, 18, 20, 17};
ui.drawGraphSamples()
.line(0)
.samples(samples, 6)
.thickness(2)
.color(ui.rgb(0, 255, 140))
.range(0, 100);
drawGraphSamples()рисует переданный массив сразу, не накапливает внутреннюю историю точек. Для streaming-режима с накоплением используйтеdrawGraphLine()updateGraphSamples()использует тот же API, но подходит для локального in-place обновления
Для Oscilloscope эти настройки задаются прямо у сетки:
ui.drawGraphGrid()
.pos(center, center)
.size(200, 170)
.direction(Oscilloscope)
.scope(2000, 100) // частота входных samples и длительность окна в мс
.visible(0);
Что важно по lifecycle:
drawGraphGrid()задаёт активную область графика и должен вызываться в screen-callback этого экрана- buffered-график (
drawGraphLine()) живёт только пока экран реально рисует граф в текущих кадрах - если экран перестал вызывать graph API или вы ушли на другой screen, внутренние буферы графа освобождаются
- при возврате граф начинает собирать историю заново
Что важно по режимам:
LeftToRightиRightToLeftиспользуют rolling-historyOscilloscopeиспользует фиксированное окно видимых samples- если
visible(0), окно дляOscilloscopeвычисляется изrateHz * timebaseMs
15.4. Пауза графиков (freeze)
В 3-button режиме, библиотека поддерживает заморозку графика:
- короткое нажатие
Selectна экране, где рисуется граф, переключает pause/resume только для графика этого экрана; - во время паузы новые значения/массивы для графиков игнорируются, а на экране остаётся последний отрисованный кадр графа.
bool paused = ui.graphPaused(); // текущее состояние
bool toggled = ui.GraphPauseToggled(); // одноразовое событие (true один раз после переключения)
Важно:
- механизм включён только когда реально есть 3-я кнопка (3-button режим);