Files
homeassistant_espscreen/components/smart_display/alert_overlay.h
T
Max Gramser 378d0b3e61 Build each screen in one language: the screen texts from the translations, European letters in the fonts, and Language & region in the app
The firmware takes its texts from screen_manager/translations/<code>.json at build time (components/smart_display:
one table in main.cpp, keys in screen_text_keys.h), and the fonts carry every letter European languages write with.
The app keeps Settings -> Language & region (language, clock, numbers) for every screen, writes LANGUAGE into the
screens' YAML and sends the clock and the number format in the layout message; the per-screen 24-hour clock goes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:24:19 +02:00

74 lines
3.1 KiB
C++

#pragma once
#include <cstddef>
#include <cstdint>
#include <string>
#include "screen_text.h"
#include "tile_icon.h"
#include "tile_icon_names.h"
#include "tile_palette.h"
// The card a Home Assistant automation puts over the whole screen (api action
// show_alert). Pure text and lookups so both boards apply the same rules and
// tests/test_alert_overlay.cpp can check them; the profiles do the LVGL work.
namespace screen_alert {
constexpr const char *FALLBACK_ICON = "alert-outline";
constexpr const char *FALLBACK_BUTTON = "OK";
constexpr uint32_t DEFAULT_CARD_COLOR = 0; // the normal card: theme::surface(0)
constexpr int MAX_TIMEOUT_SECONDS = 86400;
struct Alert {
std::string title, subtitle, icon, button; // icon: UTF-8 glyph for the icon font
uint32_t color = DEFAULT_CARD_COLOR;
int timeout_seconds = 0; // 0: stays until OK
bool flash = false;
};
inline bool blank(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; }
inline std::string trimmed(const std::string &text) {
size_t begin = 0, end = text.size();
while (begin < end && blank(text[begin])) ++begin;
while (end > begin && blank(text[end - 1])) --end;
return text.substr(begin, end - begin);
}
inline std::string lowercase(std::string text) {
for (auto &c : text) if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
return text;
}
// Cuts on a UTF-8 boundary: LVGL must never get half a code point.
inline std::string clipped(const std::string &text, size_t max_bytes) {
if (text.size() <= max_bytes) return text;
size_t end = max_bytes;
while (end > 0 && (static_cast<unsigned char>(text[end]) & 0xC0) == 0x80) --end;
return text.substr(0, end);
}
// "doorbell", "mdi:doorbell" or the hex codepoint "F12E6" of a glyph the fonts carry;
// anything else draws the warning triangle.
inline uint32_t icon_codepoint(const std::string &value) {
std::string name = lowercase(trimmed(value));
if (name.rfind("mdi:", 0) == 0) name.erase(0, 4);
if (uint32_t code = tile_icon::named(name)) return code;
const uint32_t code = tile_icon::codepoint(name);
if (code && tile_icon::carried(code)) return code;
return tile_icon::named(FALLBACK_ICON);
}
inline int clamp_timeout(int seconds) {
return seconds < 0 ? 0 : seconds > MAX_TIMEOUT_SECONDS ? MAX_TIMEOUT_SECONDS : seconds;
}
inline Alert make(const std::string &title, const std::string &subtitle, const std::string &icon,
const std::string &color, const std::string &button, int timeout, bool flash,
size_t title_max, size_t subtitle_max, size_t button_max) {
Alert alert;
alert.title = clipped(trimmed(title), title_max);
if (alert.title.empty()) alert.title = screen_text::tr(screen_text::txt::alert_notification);
alert.subtitle = clipped(trimmed(subtitle), subtitle_max);
alert.button = clipped(trimmed(button), button_max);
if (alert.button.empty()) alert.button = FALLBACK_BUTTON;
alert.icon = tile_icon::utf8(icon_codepoint(icon));
const uint32_t card = tile_palette::color(lowercase(trimmed(color)));
alert.color = card ? card : DEFAULT_CARD_COLOR;
alert.timeout_seconds = clamp_timeout(timeout);
alert.flash = flash;
return alert;
}
} // namespace screen_alert