Public Access
Add dynamic pages with clock, date, Home Assistant values and fonts
The hardcoded clock is now the built-in dynamic page "Clock". A dynamic page has a background bitmap per time of day and up to 8 elements (clock, date, Home Assistant value, text), each with position, alignment, font and overlay. Pages are drawn on the ESP32, also for the web previews. - Pages tab in the web UI: page editor with drag and drop, font upload (TTF/OTF converted in the browser to the FDF1 dot font format) - Built-in fonts Small, Normal, Large and Full height (generated by tools/make_builtin_fonts.py), built-in bitmaps Pikachu and Pikachu yawning - Home Assistant values over the REST API (URL and token in the HA tab) - Settings version 4: evening settings removed (now time slots of the Clock page), old records are converted - HA Mode dropdown lists the dynamic pages and bitmaps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1d3b084de1
commit
0411804a2f
49 files changed
+3058
-331
No files matched your search
@@ -0,0 +1,114 @@
|
||||
#include "display/font_store.h"
|
||||
|
||||
#include <LittleFS.h>
|
||||
#include <map>
|
||||
|
||||
#include "display/bitmap_store.h"
|
||||
#include "logic/bitmap_format.h"
|
||||
#include "logic/builtin_fonts.h"
|
||||
|
||||
static const char *DIR = "/fonts";
|
||||
static const char *EXTENSION = ".fnt";
|
||||
|
||||
static uint32_t revision = 0;
|
||||
// Loaded fonts by name; cleared when a font is saved or deleted
|
||||
static std::map<String, std::vector<uint8_t>> cache;
|
||||
|
||||
static String path(const char *name)
|
||||
{
|
||||
return String(DIR) + "/" + name + EXTENSION;
|
||||
}
|
||||
|
||||
std::vector<FontInfo> listFonts()
|
||||
{
|
||||
std::vector<FontInfo> list;
|
||||
for (int i = 0; i < BUILTIN_FONT_COUNT; i++)
|
||||
list.push_back({BUILTIN_FONTS[i].name, true, (uint8_t)fontHeight(builtinFont(BUILTIN_FONTS[i]))});
|
||||
for (const String &name : storedNames(DIR, EXTENSION))
|
||||
if (!findBuiltinFont(name.c_str()))
|
||||
list.push_back({name, false, (uint8_t)fontHeight(findFont(name.c_str()))});
|
||||
return list;
|
||||
}
|
||||
|
||||
uint32_t fontListRevision()
|
||||
{
|
||||
return revision;
|
||||
}
|
||||
|
||||
bool fontExists(const char *name)
|
||||
{
|
||||
if (findBuiltinFont(name))
|
||||
return true;
|
||||
return bitmapStorageAvailable() && validBitmapName(name) && LittleFS.exists(path(name));
|
||||
}
|
||||
|
||||
Font findFont(const char *name)
|
||||
{
|
||||
const BuiltinFont *builtin = findBuiltinFont(name);
|
||||
if (builtin)
|
||||
return builtinFont(*builtin);
|
||||
|
||||
Font font;
|
||||
if (!bitmapStorageAvailable() || !validBitmapName(name))
|
||||
return font;
|
||||
auto cached = cache.find(name);
|
||||
if (cached == cache.end())
|
||||
{
|
||||
if (cache.size() >= 16)
|
||||
cache.clear();
|
||||
std::vector<uint8_t> data;
|
||||
File file;
|
||||
if (LittleFS.exists(path(name))) // open() logs an error for a missing file
|
||||
file = LittleFS.open(path(name), "r");
|
||||
if (file && file.size() <= FONT_MAX_SIZE)
|
||||
{
|
||||
data.resize(file.size());
|
||||
if (file.read(data.data(), data.size()) != data.size() || !validFont(data.data(), data.size()))
|
||||
data.clear();
|
||||
}
|
||||
if (file)
|
||||
file.close();
|
||||
cached = cache.emplace(name, std::move(data)).first; // also remembers a missing font
|
||||
}
|
||||
if (!cached->second.empty())
|
||||
{
|
||||
font.data = cached->second.data();
|
||||
font.size = cached->second.size();
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
const char *saveFont(const char *name, const uint8_t *data, size_t size)
|
||||
{
|
||||
if (!validBitmapName(name))
|
||||
return "Invalid name: use 1-24 letters, digits, spaces, - or _";
|
||||
if (findBuiltinFont(name))
|
||||
return "A built-in font has this name";
|
||||
if (!validFont(data, size))
|
||||
return "Invalid font data";
|
||||
if (!bitmapStorageAvailable())
|
||||
return "No storage (install the new flash layout over USB, see README)";
|
||||
if (!LittleFS.exists(path(name)) && storedNames(DIR, EXTENSION).size() >= (size_t)MAX_FONTS)
|
||||
return "Too many fonts, delete some first";
|
||||
|
||||
File file = LittleFS.open(path(name), "w");
|
||||
bool ok = file && file.write(data, size) == size;
|
||||
if (file)
|
||||
file.close();
|
||||
cache.clear();
|
||||
revision++;
|
||||
return ok ? nullptr : "Could not write the file (storage full?)";
|
||||
}
|
||||
|
||||
const char *deleteFont(const char *name)
|
||||
{
|
||||
if (findBuiltinFont(name))
|
||||
return "Built-in fonts can't be deleted";
|
||||
if (!bitmapStorageAvailable() || !validBitmapName(name) || !LittleFS.exists(path(name)))
|
||||
return "Font not found";
|
||||
if (!LittleFS.remove(path(name)))
|
||||
return "Could not delete the file";
|
||||
cache.clear();
|
||||
revision++;
|
||||
return nullptr;
|
||||
}
|
||||
Reference in new issue
Block a user