fix: redesign X3's UTC offset picker so it is easier to use (#2205)
## Summary * **What is the goal of this PR?** Improve the X3 clock UTC offset picker so it is clearer which part of the offset will change when the user presses up/down. * **What changes are included?** Replaces the single underlined `UTC +/- H:MM` string with separate editable fields for the sign, hour, and minutes. The editable fields are spaced out, outlined, and the currently selected field is highlighted with a light gray background. ## Additional Context * This is a render-only UI change in `ClockOffsetActivity`; it does not change how UTC offsets are stored, clamped, saved, or applied. * The selected field still cycles with Confirm, and up/down still adjusts the active sign/hour/minute value. Screenshot of **current design** showing the sign (+/-) highlighted: <img width="264" height="396" alt="old utc offset" src="https://github.com/user-attachments/assets/c88d69ea-089c-47da-b5b3-ed0ebda5b112" /> Screenshot of **new design** showing the sign (+/-) highlighted: <img width="264" height="396" alt="utc offset fix" src="https://github.com/user-attachments/assets/f51da8b3-9aab-4f48-a9d1-21c828c01499" /> --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES >**_
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
#include <HalClock.h>
|
#include <HalClock.h>
|
||||||
#include <I18n.h>
|
#include <I18n.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
@@ -146,72 +147,54 @@ void ClockOffsetActivity::render(RenderLock&&) {
|
|||||||
|
|
||||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_CLOCK_UTC_OFFSET));
|
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_CLOCK_UTC_OFFSET));
|
||||||
|
|
||||||
// Build the offset string. Use a generous font and centre it.
|
|
||||||
char offsetBuf[16];
|
|
||||||
snprintf(offsetBuf, sizeof(offsetBuf), "UTC %c %d:%02d", sign == 1 ? '-' : '+', hours,
|
|
||||||
minutesQuarter * MINUTES_PER_QUARTER);
|
|
||||||
|
|
||||||
const int centreY = pageHeight / 2 - 40;
|
const int centreY = pageHeight / 2 - 40;
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, centreY, offsetBuf, true, EpdFontFamily::BOLD);
|
auto widthOf = [&](const char* s) { return renderer.getTextWidth(UI_12_FONT_ID, s, EpdFontFamily::BOLD); };
|
||||||
|
constexpr int fieldPaddingX = 6;
|
||||||
|
constexpr int labelGap = 16;
|
||||||
|
constexpr int fieldGap = 12;
|
||||||
|
constexpr int colonGap = 5;
|
||||||
|
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
||||||
|
const int fieldHeight = lineHeight + 2;
|
||||||
|
|
||||||
// Underline / caret under the active field. Compute positions by measuring substrings of the
|
char signStr[2] = {sign == 1 ? '-' : '+', '\0'};
|
||||||
// formatted string so the caret follows the font glyph widths exactly.
|
|
||||||
// Field substrings:
|
|
||||||
// "UTC " -> prefix
|
|
||||||
// "{+/-}" -> sign
|
|
||||||
// " "
|
|
||||||
// "{hours}" -> hours
|
|
||||||
// ":"
|
|
||||||
// "{mm}" -> minutes
|
|
||||||
auto widthOf = [&](const char* s) { return renderer.getTextWidth(UI_12_FONT_ID, s); };
|
|
||||||
const int totalWidth = widthOf(offsetBuf);
|
|
||||||
const int leftEdge = (pageWidth - totalWidth) / 2;
|
|
||||||
|
|
||||||
// Locate each field by reformatting prefixes.
|
|
||||||
char prefixSign[16];
|
|
||||||
snprintf(prefixSign, sizeof(prefixSign), "UTC ");
|
|
||||||
const int signX = leftEdge + widthOf(prefixSign);
|
|
||||||
|
|
||||||
char prefixHours[16];
|
|
||||||
snprintf(prefixHours, sizeof(prefixHours), "UTC %c ", sign == 1 ? '-' : '+');
|
|
||||||
const int hoursX = leftEdge + widthOf(prefixHours);
|
|
||||||
|
|
||||||
char prefixMinutes[16];
|
|
||||||
snprintf(prefixMinutes, sizeof(prefixMinutes), "UTC %c %d:", sign == 1 ? '-' : '+', hours);
|
|
||||||
const int minutesX = leftEdge + widthOf(prefixMinutes);
|
|
||||||
|
|
||||||
// Width of each field substring for the caret span.
|
|
||||||
const int signW = widthOf(sign == 1 ? "-" : "+");
|
|
||||||
char hoursStr[8];
|
char hoursStr[8];
|
||||||
snprintf(hoursStr, sizeof(hoursStr), "%d", hours);
|
snprintf(hoursStr, sizeof(hoursStr), "%d", hours);
|
||||||
const int hoursW = widthOf(hoursStr);
|
|
||||||
char minutesStr[8];
|
char minutesStr[8];
|
||||||
snprintf(minutesStr, sizeof(minutesStr), "%02d", minutesQuarter * MINUTES_PER_QUARTER);
|
snprintf(minutesStr, sizeof(minutesStr), "%02d", minutesQuarter * MINUTES_PER_QUARTER);
|
||||||
const int minutesW = widthOf(minutesStr);
|
|
||||||
|
|
||||||
int caretX = 0;
|
const int labelWidth = widthOf("UTC");
|
||||||
int caretW = 0;
|
const int signBoxW = std::max(widthOf("+"), widthOf("-")) + fieldPaddingX * 2;
|
||||||
switch (activeField) {
|
const int hoursBoxW = std::max(widthOf("14"), widthOf("12")) + fieldPaddingX * 2;
|
||||||
case FIELD_SIGN:
|
const int colonWidth = widthOf(":");
|
||||||
caretX = signX;
|
const int minutesBoxW = std::max({widthOf("00"), widthOf("15"), widthOf("30"), widthOf("45")}) + fieldPaddingX * 2;
|
||||||
caretW = signW;
|
const int totalWidth =
|
||||||
break;
|
labelWidth + labelGap + signBoxW + fieldGap + hoursBoxW + colonGap + colonWidth + colonGap + minutesBoxW;
|
||||||
case FIELD_HOURS:
|
|
||||||
caretX = hoursX;
|
int x = (pageWidth - totalWidth) / 2;
|
||||||
caretW = hoursW;
|
renderer.drawText(UI_12_FONT_ID, x, centreY, "UTC", true, EpdFontFamily::BOLD);
|
||||||
break;
|
x += labelWidth + labelGap;
|
||||||
case FIELD_MINUTES:
|
|
||||||
caretX = minutesX;
|
auto drawField = [&](const char* text, const int boxX, const int boxWidth, const Field field) {
|
||||||
caretW = minutesW;
|
const bool selected = activeField == field;
|
||||||
break;
|
renderer.fillRectDither(boxX, centreY, boxWidth, fieldHeight, selected ? Color::LightGray : Color::White);
|
||||||
default:
|
renderer.drawRect(boxX, centreY, boxWidth, fieldHeight, true);
|
||||||
break;
|
if (selected) {
|
||||||
}
|
renderer.drawRect(boxX + 1, centreY + 1, boxWidth - 2, fieldHeight - 2, true);
|
||||||
// Caret drawn as a short bar below the active field.
|
}
|
||||||
const int caretY = centreY + 10;
|
const int textX = boxX + (boxWidth - widthOf(text)) / 2;
|
||||||
for (int dy = 0; dy < 2; dy++) {
|
renderer.drawText(UI_12_FONT_ID, textX, centreY, text, true, EpdFontFamily::BOLD);
|
||||||
renderer.drawLine(caretX, caretY + dy, caretX + caretW, caretY + dy);
|
};
|
||||||
}
|
|
||||||
|
drawField(signStr, x, signBoxW, FIELD_SIGN);
|
||||||
|
x += signBoxW + fieldGap;
|
||||||
|
|
||||||
|
drawField(hoursStr, x, hoursBoxW, FIELD_HOURS);
|
||||||
|
x += hoursBoxW + colonGap;
|
||||||
|
|
||||||
|
renderer.drawText(UI_12_FONT_ID, x, centreY, ":", true, EpdFontFamily::BOLD);
|
||||||
|
x += colonWidth + colonGap;
|
||||||
|
|
||||||
|
drawField(minutesStr, x, minutesBoxW, FIELD_MINUTES);
|
||||||
|
|
||||||
// Live preview of the resulting wall-clock time, so users can verify against a watch.
|
// Live preview of the resulting wall-clock time, so users can verify against a watch.
|
||||||
if (halClock.isAvailable()) {
|
if (halClock.isAvailable()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user