feat: metadata overlay tweaks — location badge, rounded corners, translucent option
- Location gets its own badge in the lower-left corner (separate from the main date/time/people/camera badge at the configured position) - All overlay badges now use rounded corners (radius 4px) via fillRoundRect instead of fillRect - New setting: 'Translucent background?' checkbox in web UI When enabled, badge backgrounds use a checkerboard pattern (every other pixel black) instead of solid black, creating a 50% opacity effect that lets the underlying image show through - Refactored all badge drawing through a single drawOverlayBadge() helper (metadata, sleep indicator, battery indicator) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -130,7 +130,7 @@ void DisplayManager::showMetadataInRegion(const AssetInfo& info, uint8_t metaFla
|
||||
const String& dateFmt, const String& timeFmt) {
|
||||
if (metaFlags == 0) return;
|
||||
|
||||
// Build metadata string
|
||||
// Build metadata string (excluding location — it gets its own badge)
|
||||
String metaText = "";
|
||||
|
||||
// Parse ISO datetime (YYYY-MM-DDTHH:MM:SS) into struct tm for strftime
|
||||
@@ -158,10 +158,6 @@ void DisplayManager::showMetadataInRegion(const AssetInfo& info, uint8_t metaFla
|
||||
// Fallback if datetime is too short for full parse
|
||||
metaText += info.dateTime.substring(0, 10);
|
||||
}
|
||||
if ((metaFlags & META_LOCATION) && info.city.length() > 0) {
|
||||
if (metaText.length() > 0) metaText += " | ";
|
||||
metaText += info.city;
|
||||
}
|
||||
if ((metaFlags & META_PEOPLE) && !info.people.empty()) {
|
||||
if (metaText.length() > 0) metaText += " | ";
|
||||
for (size_t i = 0; i < info.people.size(); i++) {
|
||||
@@ -177,71 +173,151 @@ void DisplayManager::showMetadataInRegion(const AssetInfo& info, uint8_t metaFla
|
||||
metaText += info.camera;
|
||||
}
|
||||
|
||||
if (metaText.length() == 0) return;
|
||||
// Location gets its own badge in lower-left
|
||||
String locationText = "";
|
||||
if ((metaFlags & META_LOCATION) && info.city.length() > 0) {
|
||||
locationText = info.city;
|
||||
}
|
||||
|
||||
if (metaText.length() == 0 && locationText.length() == 0) return;
|
||||
|
||||
static constexpr uint16_t MARGIN = 8;
|
||||
static constexpr uint16_t PAD_X = 8;
|
||||
static constexpr uint16_t PAD_Y = 5;
|
||||
static constexpr uint16_t TEXT_H = 20;
|
||||
|
||||
_display->setTextSize(1.5f);
|
||||
_display->setTextColor(TFT_WHITE);
|
||||
|
||||
switch (pos) {
|
||||
case MetaPosition::CaptionBottom:
|
||||
case MetaPosition::CaptionTop: {
|
||||
// Bar spanning the region width, positioned at region top or bottom
|
||||
uint16_t barH = 36;
|
||||
uint16_t barY = (pos == MetaPosition::CaptionTop)
|
||||
? regionY
|
||||
: regionY + regionH - barH;
|
||||
_display->fillRect(regionX, barY, regionW, barH, TFT_BLACK);
|
||||
_display->setTextDatum(middle_center);
|
||||
_display->drawString(metaText.c_str(), regionX + regionW / 2, barY + barH / 2);
|
||||
break;
|
||||
}
|
||||
case MetaPosition::OverlayTopLeft:
|
||||
case MetaPosition::OverlayTopRight:
|
||||
case MetaPosition::OverlayBottomLeft:
|
||||
case MetaPosition::OverlayBottomRight: {
|
||||
static constexpr uint16_t MARGIN = 8;
|
||||
static constexpr uint16_t PAD_X = 8;
|
||||
static constexpr uint16_t PAD_Y = 5;
|
||||
static constexpr uint16_t TEXT_H = 20;
|
||||
// Track where the main badge ends up (for location badge offset)
|
||||
uint16_t mainBadgeBottomY = 0;
|
||||
|
||||
uint16_t textW = _display->textWidth(metaText.c_str());
|
||||
uint16_t badgeW = min((uint16_t)(textW + PAD_X * 2), regionW);
|
||||
uint16_t badgeH = TEXT_H + PAD_Y * 2;
|
||||
|
||||
uint16_t bx, by;
|
||||
switch (pos) {
|
||||
case MetaPosition::OverlayTopLeft:
|
||||
bx = regionX + MARGIN;
|
||||
by = regionY + MARGIN;
|
||||
break;
|
||||
case MetaPosition::OverlayTopRight:
|
||||
bx = regionX + regionW - badgeW - MARGIN;
|
||||
by = regionY + MARGIN;
|
||||
break;
|
||||
case MetaPosition::OverlayBottomLeft:
|
||||
bx = regionX + MARGIN;
|
||||
by = regionY + regionH - badgeH - MARGIN;
|
||||
break;
|
||||
case MetaPosition::OverlayBottomRight:
|
||||
bx = regionX + regionW - badgeW - MARGIN;
|
||||
by = regionY + regionH - badgeH - MARGIN;
|
||||
break;
|
||||
default:
|
||||
bx = regionX + MARGIN;
|
||||
by = regionY + MARGIN;
|
||||
break;
|
||||
// Draw main metadata badge (date/time/people/camera) at configured position
|
||||
if (metaText.length() > 0) {
|
||||
switch (pos) {
|
||||
case MetaPosition::CaptionBottom:
|
||||
case MetaPosition::CaptionTop: {
|
||||
uint16_t barH = 36;
|
||||
uint16_t barY = (pos == MetaPosition::CaptionTop)
|
||||
? regionY
|
||||
: regionY + regionH - barH;
|
||||
_display->fillRect(regionX, barY, regionW, barH, TFT_BLACK);
|
||||
_display->setTextDatum(middle_center);
|
||||
_display->drawString(metaText.c_str(), regionX + regionW / 2, barY + barH / 2);
|
||||
break;
|
||||
}
|
||||
case MetaPosition::OverlayTopLeft:
|
||||
case MetaPosition::OverlayTopRight:
|
||||
case MetaPosition::OverlayBottomLeft:
|
||||
case MetaPosition::OverlayBottomRight: {
|
||||
uint16_t textW = _display->textWidth(metaText.c_str());
|
||||
uint16_t badgeW = min((uint16_t)(textW + PAD_X * 2), regionW);
|
||||
uint16_t badgeH = TEXT_H + PAD_Y * 2;
|
||||
|
||||
_display->fillRect(bx, by, badgeW, badgeH, TFT_BLACK);
|
||||
_display->setTextDatum(middle_left);
|
||||
_display->drawString(metaText.c_str(), bx + PAD_X, by + badgeH / 2);
|
||||
break;
|
||||
uint16_t bx, by;
|
||||
switch (pos) {
|
||||
case MetaPosition::OverlayTopLeft:
|
||||
bx = regionX + MARGIN;
|
||||
by = regionY + MARGIN;
|
||||
break;
|
||||
case MetaPosition::OverlayTopRight:
|
||||
bx = regionX + regionW - badgeW - MARGIN;
|
||||
by = regionY + MARGIN;
|
||||
break;
|
||||
case MetaPosition::OverlayBottomLeft:
|
||||
bx = regionX + MARGIN;
|
||||
by = regionY + regionH - badgeH - MARGIN;
|
||||
break;
|
||||
case MetaPosition::OverlayBottomRight:
|
||||
bx = regionX + regionW - badgeW - MARGIN;
|
||||
by = regionY + regionH - badgeH - MARGIN;
|
||||
break;
|
||||
default:
|
||||
bx = regionX + MARGIN;
|
||||
by = regionY + MARGIN;
|
||||
break;
|
||||
}
|
||||
|
||||
drawOverlayBadge(bx, by, badgeW, badgeH);
|
||||
_display->setTextDatum(middle_left);
|
||||
_display->drawString(metaText.c_str(), bx + PAD_X, by + badgeH / 2);
|
||||
mainBadgeBottomY = by + badgeH;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw location badge in lower-left corner (separate from main badge)
|
||||
if (locationText.length() > 0) {
|
||||
uint16_t locTextW = _display->textWidth(locationText.c_str());
|
||||
uint16_t locBadgeW = min((uint16_t)(locTextW + PAD_X * 2), regionW);
|
||||
uint16_t locBadgeH = TEXT_H + PAD_Y * 2;
|
||||
|
||||
uint16_t lx = regionX + MARGIN;
|
||||
uint16_t ly = regionY + regionH - locBadgeH - MARGIN;
|
||||
|
||||
// If main badge is also in bottom-left, stack location above it
|
||||
if (pos == MetaPosition::OverlayBottomLeft && metaText.length() > 0) {
|
||||
ly = ly - locBadgeH - 4;
|
||||
}
|
||||
|
||||
drawOverlayBadge(lx, ly, locBadgeW, locBadgeH);
|
||||
_display->setTextColor(TFT_WHITE);
|
||||
_display->setTextDatum(middle_left);
|
||||
_display->drawString(locationText.c_str(), lx + PAD_X, ly + locBadgeH / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager::drawBadge(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
_display->fillRect(x, y, w, h, TFT_BLACK);
|
||||
void DisplayManager::drawOverlayBadge(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
static constexpr int16_t R = 4;
|
||||
|
||||
if (!_translucent) {
|
||||
_display->fillRoundRect(x, y, w, h, R, TFT_BLACK);
|
||||
return;
|
||||
}
|
||||
|
||||
// Checkerboard pattern within rounded-rect bounds for 50% opacity effect.
|
||||
// Use fillRoundRect to create a clip mask in black, then punch holes by
|
||||
// restoring every-other pixel. Simpler: just iterate and test corner radii.
|
||||
for (uint16_t py = y; py < y + h; py++) {
|
||||
for (uint16_t px = x; px < x + w; px++) {
|
||||
if ((px + py) % 2 != 0) continue;
|
||||
|
||||
// Check if pixel is inside the rounded rect (corner exclusion)
|
||||
int16_t lx = px - x;
|
||||
int16_t ly = py - y;
|
||||
bool inside = true;
|
||||
|
||||
// Top-left corner
|
||||
if (lx < R && ly < R) {
|
||||
int16_t dx = R - 1 - lx;
|
||||
int16_t dy = R - 1 - ly;
|
||||
inside = (dx * dx + dy * dy) <= (R * R);
|
||||
}
|
||||
// Top-right corner
|
||||
else if (lx >= w - R && ly < R) {
|
||||
int16_t dx = lx - (w - R);
|
||||
int16_t dy = R - 1 - ly;
|
||||
inside = (dx * dx + dy * dy) <= (R * R);
|
||||
}
|
||||
// Bottom-left corner
|
||||
else if (lx < R && ly >= h - R) {
|
||||
int16_t dx = R - 1 - lx;
|
||||
int16_t dy = ly - (h - R);
|
||||
inside = (dx * dx + dy * dy) <= (R * R);
|
||||
}
|
||||
// Bottom-right corner
|
||||
else if (lx >= w - R && ly >= h - R) {
|
||||
int16_t dx = lx - (w - R);
|
||||
int16_t dy = ly - (h - R);
|
||||
inside = (dx * dx + dy * dy) <= (R * R);
|
||||
}
|
||||
|
||||
if (inside) {
|
||||
_display->writePixel(px, py, TFT_BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager::showSleepIndicator(MetaPosition metaPos) {
|
||||
@@ -281,7 +357,7 @@ void DisplayManager::showSleepIndicator(MetaPosition metaPos) {
|
||||
break;
|
||||
}
|
||||
|
||||
drawBadge(bx, by, badgeW, badgeH);
|
||||
drawOverlayBadge(bx, by, badgeW, badgeH);
|
||||
_display->setTextColor(TFT_WHITE);
|
||||
_display->setTextDatum(middle_center);
|
||||
_display->drawString("SLP", bx + badgeW / 2, by + badgeH / 2);
|
||||
@@ -324,7 +400,7 @@ void DisplayManager::showBatteryIndicator(uint8_t percent, MetaPosition metaPos)
|
||||
}
|
||||
|
||||
// Draw badge background
|
||||
drawBadge(bx, by, badgeW, badgeH);
|
||||
drawOverlayBadge(bx, by, badgeW, badgeH);
|
||||
|
||||
// Draw battery outline (white rect with 1px border)
|
||||
uint16_t iconX = bx + PAD_X;
|
||||
|
||||
Reference in New Issue
Block a user