Public Access
Progress bars can show the percentage on top
The percentage (rounded down, in the element's font) is centred inside the bar and its dots invert the bar, so it reads on the filled and the empty part. It uses the bar's unused format and font fields, so the page format stays the same. The Bad page shows it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9959c1aa74
commit
948087077d
9 files changed
+187
-9
No files matched your search
+1
-1
@@ -118,7 +118,7 @@ const uint32_t SETUP_AP_DELAY = 20000; // open the AP when WiFi fails thi
|
||||
const uint32_t WIFI_CONNECT_TIMEOUT = 30000;
|
||||
const uint32_t WIFI_RESTART_TIME = 10UL * 60 * 1000;
|
||||
|
||||
#define FIRMWARE_VERSION "2.15.1"
|
||||
#define FIRMWARE_VERSION "2.16.0"
|
||||
|
||||
// Home Assistant device id (do not change: Home Assistant's entities are linked to it)
|
||||
const uint8_t HA_DEVICE_ID[] = {0x34, 0x86, 0x5d, 0x3a, 0x08, 0x28};
|
||||
|
||||
@@ -150,6 +150,8 @@ static uint8_t formatCount(const PageElement &e)
|
||||
return COUNTDOWN_FORMAT_COUNT;
|
||||
case ElementType::Stopwatch:
|
||||
return STOPWATCH_FORMAT_COUNT;
|
||||
case ElementType::Bar:
|
||||
return BAR_FORMAT_COUNT;
|
||||
default:
|
||||
return 255; // not used
|
||||
}
|
||||
@@ -788,6 +790,20 @@ int barFill(const PageElement &e, const char *ha_value, int fill_width)
|
||||
return n;
|
||||
}
|
||||
|
||||
void barPercent(const PageElement &e, const char *ha_value, char *out, size_t size)
|
||||
{
|
||||
double v;
|
||||
if (!ha_value || !parseNumber(ha_value, v))
|
||||
{
|
||||
snprintf(out, size, "--");
|
||||
return;
|
||||
}
|
||||
double percent = v * e.factor / e.bar_max * 100;
|
||||
// Rounded down, so 100% only shows when the bar is full; + 1e-9 keeps 0.29 x 100 at 29
|
||||
int n = !(percent > 0) ? 0 : percent >= 100 ? 100 : (int)(percent + 1e-9);
|
||||
snprintf(out, size, "%d%%", n);
|
||||
}
|
||||
|
||||
bool elementShowsSeconds(const PageElement &e, const LocalTime &t, bool stopwatch_running)
|
||||
{
|
||||
switch (e.type)
|
||||
|
||||
@@ -98,6 +98,11 @@ const uint8_t STOPWATCH_H_MM = 1; // 0:04 (hours and minutes)
|
||||
const uint8_t STOPWATCH_H_MM_SS = 2; // 0:04:05
|
||||
const uint8_t STOPWATCH_FORMAT_COUNT = 3;
|
||||
|
||||
// Progress bar formats
|
||||
const uint8_t BAR_PLAIN = 0; // only the bar
|
||||
const uint8_t BAR_PERCENT = 1; // with the percentage on top (inverting the bar below it)
|
||||
const uint8_t BAR_FORMAT_COUNT = 2;
|
||||
|
||||
// How a Home Assistant value is shown
|
||||
enum class HaShow : uint8_t
|
||||
{
|
||||
@@ -152,7 +157,7 @@ struct PageElement
|
||||
Align align = Align::Left;
|
||||
Overlay overlay = Overlay::OnTop;
|
||||
char font[PAGE_NAME_SIZE] = "Normal";
|
||||
uint8_t format = 0; // CLOCK_*, DATE_*, COUNTDOWN_*, STOPWATCH_*, HaValue: DURATION_* or TIMESTAMP_*
|
||||
uint8_t format = 0; // CLOCK_*, DATE_*, COUNTDOWN_*, STOPWATCH_*, BAR_*, HaValue: DURATION_* or TIMESTAMP_*
|
||||
Language language = Language::Dutch;
|
||||
int8_t decimals = DECIMALS_AS_IS;
|
||||
// Text: the text; HaValue, Countdown, Stopwatch: shown before the value; Image: the
|
||||
@@ -222,6 +227,8 @@ void elementText(const PageElement &element, const LocalTime &time, const Elemen
|
||||
// Bar: how many dots of fill_width are filled for this Home Assistant value (value x
|
||||
// factor / bar_max, 0 to fill_width); 0 when the value is unknown or not a number
|
||||
int barFill(const PageElement &element, const char *ha_value, int fill_width);
|
||||
// Bar with BAR_PERCENT: the text on it, e.g. "42%" (rounded down, 0-100), "--" when unknown
|
||||
void barPercent(const PageElement &element, const char *ha_value, char *out, size_t out_size);
|
||||
|
||||
// Whether the element's text shows seconds now (it then changes every second)
|
||||
bool elementShowsSeconds(const PageElement &element, const LocalTime &time, bool stopwatch_running);
|
||||
|
||||
@@ -90,6 +90,38 @@ static void drawImage(const PageElement &e, PageResources &resources, uint8_t *b
|
||||
}
|
||||
}
|
||||
|
||||
// Bar with BAR_PERCENT: the percentage in the middle of the area inside the frame, its
|
||||
// dots flip what is below them (so it shows on the filled and the empty part). Centred on
|
||||
// the dots the text really has (fonts have empty rows); left out when it doesn't fit.
|
||||
static void drawBarPercent(const PageElement &e, PageResources &resources, uint8_t *bitmap, int x, int y, int w,
|
||||
int h)
|
||||
{
|
||||
char text[8];
|
||||
barPercent(e, resources.haValue(e), text, sizeof(text));
|
||||
Font font = usableFont(resources, e.font);
|
||||
uint8_t dots[BITMAP_BYTES];
|
||||
memset(dots, 0, BITMAP_BYTES);
|
||||
drawText(dots, font, 0, 0, text, DrawMode::Set);
|
||||
int left = BITMAP_WIDTH, right = -1, top = BITMAP_HEIGHT, bottom = -1;
|
||||
for (int yy = 0; yy < BITMAP_HEIGHT; yy++)
|
||||
for (int xx = 0; xx < BITMAP_WIDTH; xx++)
|
||||
if (bitmapPixel(dots, xx, yy))
|
||||
{
|
||||
left = xx < left ? xx : left;
|
||||
right = xx > right ? xx : right;
|
||||
top = yy < top ? yy : top;
|
||||
bottom = yy > bottom ? yy : bottom;
|
||||
}
|
||||
int tw = right - left + 1, th = bottom - top + 1;
|
||||
if (right < 0 || tw > w || th > h)
|
||||
return;
|
||||
int ox = x + (w - tw) / 2 - left, oy = y + (h - th) / 2 - top;
|
||||
for (int yy = top; yy <= bottom; yy++)
|
||||
for (int xx = left; xx <= right; xx++)
|
||||
if (bitmapPixel(dots, xx, yy))
|
||||
setBitmapPixel(bitmap, ox + xx, oy + yy, !bitmapPixel(bitmap, ox + xx, oy + yy));
|
||||
}
|
||||
|
||||
// Bar: a frame (optional) filled from the left by the Home Assistant value
|
||||
static void drawBar(const PageElement &e, PageResources &resources, uint8_t *bitmap, ElementBox &box)
|
||||
{
|
||||
@@ -120,6 +152,8 @@ static void drawBar(const PageElement &e, PageResources &resources, uint8_t *bit
|
||||
int bx = left + x, by = e.y + y;
|
||||
setBitmapPixel(bitmap, bx, by, e.overlay == Overlay::Invert ? !bitmapPixel(bitmap, bx, by) : true);
|
||||
}
|
||||
if (e.format == BAR_PERCENT)
|
||||
drawBarPercent(e, resources, bitmap, left + inner, e.y + inner, w - 2 * inner, h - 2 * inner);
|
||||
}
|
||||
|
||||
// A text element; returns the ms until it scrolls on (0: it doesn't scroll)
|
||||
|
||||
@@ -429,6 +429,8 @@ function peElems(){const d=$('pe_elems');d.innerHTML='';d.append(mk('datalist',{
|
||||
row(field('Full at',inpf(e,'bar_max',{style:'width:6em'})),field('Multiply by',inpf(e,'factor',{style:'width:6em'})),
|
||||
field('Width',inp(e,'bar_width','number',{style:'width:4em',min:1,max:84})),field('Height',inp(e,'bar_height','number',{style:'width:4em',min:1,max:19})),
|
||||
chk(e,'bar_frame','frame (from 3 x 3 dots)'));
|
||||
const pc=mk('input',{type:'checkbox',checked:e.format==1});pc.onchange=()=>{e.format=pc.checked?1:0;if(pc.checked&&same(e.font,'Normal'))e.font='Small';peElems();pePreview()};
|
||||
row(mk('label',{},pc,' percentage on top (inverts the bar below it; only shown when it fits inside, Small needs a height of 7 with frame)'));
|
||||
rows.push(mk('p',{className:'hint',textContent:'Filled from the left: value x multiplier / full at (e.g. a percentage: full at 100; seconds of a duration: use an entity with the percentage). Unknown values show an empty bar.'}))}
|
||||
const fonts=fontList.map(f=>[f.name,f.name+' ('+f.height+')']);if(!fontList.some(f=>same(f.name,e.font)))fonts.push([e.font,e.font+' (gone)']);
|
||||
const pos=[field('X',inp(e,'x','number',{style:'width:5em'})),field('Y',inp(e,'y','number',{style:'width:5em'}))];
|
||||
@@ -437,7 +439,7 @@ function peElems(){const d=$('pe_elems');d.innerHTML='';d.append(mk('datalist',{
|
||||
row(...pos,field('Align',sel(e,'align',[['left','left'],['center','center'],['right','right']])),field('Font',sel(e,'font',fonts)),ovl);
|
||||
row(field('Scroll in a window of',inp(e,'scroll_width','number',{style:'width:5em',min:0,max:168})),'dots (0: off), at',
|
||||
inpf(e,'scroll_speed',{style:'width:5em',min:0.05,max:30}),'dots per second (0.25: one every 4 s)')}
|
||||
else if(e.type=='bar')row(...pos,field('Align',sel(e,'align',[['left','left'],['center','center'],['right','right']])),ovl);
|
||||
else if(e.type=='bar')row(...pos,field('Align',sel(e,'align',[['left','left'],['center','center'],['right','right']])),...(e.format==1?[field('Font',sel(e,'font',fonts))]:[]),ovl);
|
||||
else row(...pos,ovl);
|
||||
box.append(...rows);d.append(box)})}
|
||||
function peAdd(t){if(pe.page.elements.length>=16){msg('16 elements at most',true);return}pe.page.elements.push(newEl(t));pe.sel=pe.page.elements.length-1;peElems();pePreview()}
|
||||
|
||||
Reference in new issue
Block a user