Scroll speeds below 1 dot per second

scroll_speed is now a float (0.05-30 dots per second, e.g. 0.25 = one dot
every 4 s). Page format 5; version 4 pages are converted, version 3 goes
through version 4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-16 16:23:16 +02:00
1 parent 1282d16c0b
commit e0a89cec99
17 files changed
+148 -21

No files matched your search

+105 -10
View File
@@ -8,7 +8,7 @@
static const char *DIR = "/pages";
static const char *EXTENSION = ".pg";
// Increase when DynamicPage changes; older files are ignored unless converted below
static const uint16_t VERSION = 4;
static const uint16_t VERSION = 5;
struct StoredPage
{
@@ -149,8 +149,89 @@ static void convert(const Page &old, DynamicPage &page)
}
} // namespace v2
// Version 3 (before the progress bar): the elements lacked the fields from `bar_width` on.
// The rest has the same layout, so it is copied as a block.
// Version 4 (before scroll speeds below 1): scroll_speed was a whole number (uint8_t).
// Frozen copy, converted when loaded. Version 3 is converted through it.
namespace v4
{
struct Element
{
ElementType type;
int16_t x, y;
Align align;
Overlay overlay;
char font[PAGE_NAME_SIZE];
uint8_t format;
Language language;
int8_t decimals;
char text[ELEMENT_TEXT_SIZE];
char suffix[ELEMENT_SUFFIX_SIZE];
char entity[ELEMENT_ENTITY_SIZE];
char attribute[ELEMENT_ATTRIBUTE_SIZE];
float factor;
uint8_t scroll_width;
uint8_t scroll_speed;
uint32_t target;
HaShow show;
uint8_t input_unit;
bool auto_unit;
bool absolute;
char map[ELEMENT_MAP_SIZE];
uint8_t bar_width = 40;
uint8_t bar_height = 3;
float bar_max = 100;
bool bar_frame = true;
};
struct Page
{
uint8_t slot_count;
PageSlot slots[PAGE_SLOTS];
uint8_t element_count;
Element elements[PAGE_ELEMENTS];
};
struct Stored
{
uint16_t version;
uint16_t size;
Page page;
};
// Up to scroll_speed the layout is the same, so that part is copied as a block
static const size_t BEFORE_SPEED = offsetof(Element, scroll_speed);
static_assert(offsetof(PageElement, scroll_speed) >= BEFORE_SPEED &&
offsetof(PageElement, scroll_width) == offsetof(Element, scroll_width),
"same layout up to scroll_width");
static void convert(const Page &old, DynamicPage &page)
{
page = DynamicPage();
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
for (int i = 0; i < PAGE_ELEMENTS; i++)
{
const Element &o = old.elements[i];
PageElement &e = page.elements[i];
memcpy((void *)&e, &o, BEFORE_SPEED);
e.scroll_speed = o.scroll_speed;
e.target = o.target;
e.show = o.show;
e.input_unit = o.input_unit;
e.auto_unit = o.auto_unit;
e.absolute = o.absolute;
memcpy(e.map, o.map, sizeof(e.map));
e.bar_width = o.bar_width;
e.bar_height = o.bar_height;
e.bar_max = o.bar_max;
e.bar_frame = o.bar_frame;
}
}
} // namespace v4
// Version 3 (before the progress bar): the version 4 elements without the fields from
// `bar_width` on. The rest has the same layout, so it is copied as a block, then
// converted like version 4.
namespace v3
{
struct Element
@@ -194,21 +275,23 @@ struct Stored
};
static const size_t COMMON = offsetof(Element, map) + sizeof(Element::map);
static_assert(offsetof(PageElement, map) == offsetof(Element, map), "same layout up to map");
static_assert(offsetof(v4::Element, map) == offsetof(Element, map), "same layout up to map");
static void convert(const Page &old, DynamicPage &page)
{
page = DynamicPage();
page.slot_count = old.slot_count;
memcpy(page.slots, old.slots, sizeof(page.slots));
page.element_count = old.element_count;
v4::Page *mid = new v4::Page; // bar fields at their defaults
mid->slot_count = old.slot_count;
memcpy(mid->slots, old.slots, sizeof(mid->slots));
mid->element_count = old.element_count;
for (int i = 0; i < PAGE_ELEMENTS; i++)
memcpy((void *)&page.elements[i], &old.elements[i], COMMON); // the bar fields keep their defaults
memcpy((void *)&mid->elements[i], &old.elements[i], COMMON);
v4::convert(*mid, page);
delete mid;
}
} // namespace v3
static_assert(sizeof(v1::Stored) != sizeof(v2::Stored) && sizeof(v2::Stored) != sizeof(v3::Stored) &&
sizeof(v3::Stored) != sizeof(StoredPage),
sizeof(v3::Stored) != sizeof(v4::Stored) && sizeof(v4::Stored) != sizeof(StoredPage),
"the versions are told apart by their size");
static uint32_t revision = 0;
@@ -269,6 +352,18 @@ bool loadPage(const char *name, DynamicPage &page)
page = stored->page;
delete stored;
}
else if (size == sizeof(v4::Stored))
{
v4::Stored *stored = new v4::Stored;
ok = file.read((uint8_t *)stored, sizeof(v4::Stored)) == sizeof(v4::Stored) && stored->version == 4 &&
stored->size == sizeof(v4::Page);
if (ok)
{
v4::convert(stored->page, page);
ok = validatePage(page) == nullptr;
}
delete stored;
}
else if (size == sizeof(v3::Stored))
{
v3::Stored *stored = new v3::Stored;