Add scrolling, countdown, stopwatch, images, seconds, multiplier and degree sign

- Text elements can scroll in a window at a set speed; clock, countdown and
  stopwatch can show seconds. Moving pages are sent as instant frames (new
  Nano command D1) and paced by the estimated flip time.
- Countdown to a date and time (smart, days, days + time, with seconds),
  stopwatch (web UI, HA switch and reset button), image element (fixed or
  by Home Assistant state), multiplier for HA values, degree sign (char 127)
  in all fonts and the font converter.
- Page format version 2; version 1 pages are converted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-15 21:01:14 +02:00
1 parent ca8d78ea8a
commit fc6fa1a180
38 files changed
+1370 -277

No files matched your search

+29 -7
View File
@@ -5,7 +5,7 @@
#include "logic/settings.h"
static const char *const TYPES[] = {"clock", "date", "ha", "text"};
static const char *const TYPES[] = {"clock", "date", "ha", "text", "countdown", "stopwatch", "image"};
static const char *const ALIGNS[] = {"left", "center", "right"};
static const char *const OVERLAYS[] = {"top", "box", "invert"};
static const char *const LANGUAGES[] = {"nl", "en"};
@@ -18,6 +18,15 @@ template <size_t N> static int indexOf(const char *const (&names)[N], const char
return -1;
}
static const char *DEGREE_UTF8 = "\xC2\xB0";
static String withDegrees(const char *text)
{
String s = text;
s.replace(String(DEGREE_SIGN), DEGREE_UTF8);
return s;
}
void pageToJson(const DynamicPage &page, JsonObject out)
{
char time[6];
@@ -43,20 +52,27 @@ void pageToJson(const DynamicPage &page, JsonObject out)
o["format"] = e.format;
o["language"] = LANGUAGES[(int)e.language];
o["decimals"] = e.decimals;
o["text"] = e.text;
o["suffix"] = e.suffix;
o["text"] = withDegrees(e.text);
o["suffix"] = withDegrees(e.suffix);
o["entity"] = e.entity;
o["attribute"] = e.attribute;
o["factor"] = e.factor;
o["scroll_width"] = e.scroll_width;
o["scroll_speed"] = e.scroll_speed;
char target[17];
formatTarget(e.target, target);
o["target"] = String(target);
}
}
// False when the text does not fit
// False when the text does not fit. The degree sign (UTF-8) becomes DEGREE_SIGN.
static bool copyText(JsonVariantConst value, char *out, size_t size)
{
const char *text = value | "";
if (strlen(text) >= size)
String text = value | "";
text.replace(DEGREE_UTF8, String(DEGREE_SIGN));
if (text.length() >= size)
return false;
strcpy(out, text);
strcpy(out, text.c_str());
return true;
}
@@ -95,6 +111,12 @@ const char *pageFromJson(JsonVariantConst in, DynamicPage &page)
e.y = constrain(o["y"] | 0, -1000, 1000);
e.format = constrain(o["format"] | 0, 0, 255);
e.decimals = constrain(o["decimals"] | (int)DECIMALS_AS_IS, -100, 100);
e.factor = o["factor"] | 1.0f;
e.scroll_width = constrain(o["scroll_width"] | 0, 0, 255);
e.scroll_speed = constrain(o["scroll_speed"] | 8, 0, 255);
const char *target = o["target"] | "";
if (e.type == ElementType::Countdown && !parseTarget(target, e.target))
return "Countdown: enter a date and time";
if (!copyText(o["font"], e.font, sizeof(e.font)) || !copyText(o["text"], e.text, sizeof(e.text)) ||
!copyText(o["suffix"], e.suffix, sizeof(e.suffix)) || !copyText(o["entity"], e.entity, sizeof(e.entity)) ||
!copyText(o["attribute"], e.attribute, sizeof(e.attribute)))