Battery header: add "Bars" display mode
Extends the battery header setting from a %/V toggle into a 3-state option (Percentage / Voltage / Bars), encoded across two option bits so existing codeplugs keep reading as Percentage. Bars renders a battery-shaped icon (outlined body + terminal nub) with up to 4 segments, each representing 25% of charge, and blinks the fill when the battery is low. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fa2ad5d0d2
commit
4ec5cfdd4c
4 changed files with 102 additions and 16 deletions
|
|
@ -129,7 +129,7 @@ typedef enum
|
||||||
{
|
{
|
||||||
BIT_INVERSE_VIDEO = (SETTINGS_BITS_BANK_0 | (1 << 0)),
|
BIT_INVERSE_VIDEO = (SETTINGS_BITS_BANK_0 | (1 << 0)),
|
||||||
BIT_PTT_LATCH = (SETTINGS_BITS_BANK_0 | (1 << 1)),
|
BIT_PTT_LATCH = (SETTINGS_BITS_BANK_0 | (1 << 1)),
|
||||||
BIT_UNUSED_2 = (SETTINGS_BITS_BANK_0 | (1 << 2)),
|
BIT_BATTERY_BARS_IN_HEADER = (SETTINGS_BITS_BANK_0 | (1 << 2)), // companion to BIT_BATTERY_VOLTAGE_IN_HEADER: encodes the "Bars" battery display mode
|
||||||
BIT_BATTERY_VOLTAGE_IN_HEADER = (SETTINGS_BITS_BANK_0 | (1 << 3)),
|
BIT_BATTERY_VOLTAGE_IN_HEADER = (SETTINGS_BITS_BANK_0 | (1 << 3)),
|
||||||
BIT_SETTINGS_UPDATED = (SETTINGS_BITS_BANK_0 | (1 << 4)),
|
BIT_SETTINGS_UPDATED = (SETTINGS_BITS_BANK_0 | (1 << 4)),
|
||||||
BIT_TX_RX_FREQ_LOCK = (SETTINGS_BITS_BANK_0 | (1 << 5)),
|
BIT_TX_RX_FREQ_LOCK = (SETTINGS_BITS_BANK_0 | (1 << 5)),
|
||||||
|
|
@ -411,6 +411,20 @@ void settingsSetOptionBit(bitfieldOptions_t bit, bool set);
|
||||||
bool settingsIsOptionBitSetFromSettings(settingsStruct_t *sets, bitfieldOptions_t bit);
|
bool settingsIsOptionBitSetFromSettings(settingsStruct_t *sets, bitfieldOptions_t bit);
|
||||||
bool settingsIsOptionBitSet(bitfieldOptions_t bit);
|
bool settingsIsOptionBitSet(bitfieldOptions_t bit);
|
||||||
|
|
||||||
|
// How the battery level is shown in the header. Encoded across two option bits
|
||||||
|
// (BIT_BATTERY_VOLTAGE_IN_HEADER / BIT_BATTERY_BARS_IN_HEADER) for backwards
|
||||||
|
// compatibility: an unset pair reads as PERCENTAGE, matching legacy behaviour.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
BATTERY_UNIT_PERCENTAGE = 0,
|
||||||
|
BATTERY_UNIT_VOLTAGE,
|
||||||
|
BATTERY_UNIT_BARS,
|
||||||
|
NUM_BATTERY_UNIT_DISPLAY
|
||||||
|
} batteryUnitDisplay_t;
|
||||||
|
|
||||||
|
batteryUnitDisplay_t settingsGetBatteryUnitDisplay(void);
|
||||||
|
void settingsSetBatteryUnitDisplay(batteryUnitDisplay_t mode);
|
||||||
|
|
||||||
void settingsSetDirty(void);
|
void settingsSetDirty(void);
|
||||||
void settingsSetVFODirty(void);
|
void settingsSetVFODirty(void);
|
||||||
void settingsSaveIfNeeded(bool immediately);
|
void settingsSaveIfNeeded(bool immediately);
|
||||||
|
|
|
||||||
|
|
@ -619,6 +619,27 @@ void settingsSetScreenDimLevel(uint8_t level)
|
||||||
settingsSetOptionBit(BIT_SCREEN_DIM_2, ((level & 4) != 0));
|
settingsSetOptionBit(BIT_SCREEN_DIM_2, ((level & 4) != 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Battery header display mode is stored across two option bits so existing
|
||||||
|
// codeplugs (both bits clear) keep reading as BATTERY_UNIT_PERCENTAGE.
|
||||||
|
batteryUnitDisplay_t settingsGetBatteryUnitDisplay(void)
|
||||||
|
{
|
||||||
|
if (settingsIsOptionBitSet(BIT_BATTERY_BARS_IN_HEADER))
|
||||||
|
{
|
||||||
|
return BATTERY_UNIT_BARS;
|
||||||
|
}
|
||||||
|
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
|
||||||
|
{
|
||||||
|
return BATTERY_UNIT_VOLTAGE;
|
||||||
|
}
|
||||||
|
return BATTERY_UNIT_PERCENTAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void settingsSetBatteryUnitDisplay(batteryUnitDisplay_t mode)
|
||||||
|
{
|
||||||
|
settingsSetOptionBit(BIT_BATTERY_VOLTAGE_IN_HEADER, (mode == BATTERY_UNIT_VOLTAGE));
|
||||||
|
settingsSetOptionBit(BIT_BATTERY_BARS_IN_HEADER, (mode == BATTERY_UNIT_BARS));
|
||||||
|
}
|
||||||
|
|
||||||
void settingsSetDirty(void)
|
void settingsSetDirty(void)
|
||||||
{
|
{
|
||||||
settingsDirty = true;
|
settingsDirty = true;
|
||||||
|
|
|
||||||
|
|
@ -314,15 +314,20 @@ static void updateScreen(bool isFirstRun)
|
||||||
#if ! defined(PLATFORM_MD9600)
|
#if ! defined(PLATFORM_MD9600)
|
||||||
case DISPLAY_BATTERY_UNIT_IN_HEADER:
|
case DISPLAY_BATTERY_UNIT_IN_HEADER:
|
||||||
leftSide = currentLanguage->battery;
|
leftSide = currentLanguage->battery;
|
||||||
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
|
switch (settingsGetBatteryUnitDisplay())
|
||||||
{
|
{
|
||||||
rightSideUnitsPrompt = PROMPT_VOLTS;
|
case BATTERY_UNIT_VOLTAGE:
|
||||||
rightSideUnitsStr = "V";
|
rightSideUnitsPrompt = PROMPT_VOLTS;
|
||||||
}
|
rightSideUnitsStr = "V";
|
||||||
else
|
break;
|
||||||
{
|
case BATTERY_UNIT_BARS:
|
||||||
rightSideUnitsPrompt = PROMPT_PERCENT;
|
// Plain literal -> rightSideVar (voice-safe), like the Screen Dim entry above.
|
||||||
rightSideUnitsStr = "%";
|
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%s", "Bars");
|
||||||
|
break;
|
||||||
|
default: // BATTERY_UNIT_PERCENTAGE
|
||||||
|
rightSideUnitsPrompt = PROMPT_PERCENT;
|
||||||
|
rightSideUnitsStr = "%";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -705,9 +710,12 @@ static void handleEvent(uiEvent_t *ev)
|
||||||
|
|
||||||
#if ! defined(PLATFORM_MD9600)
|
#if ! defined(PLATFORM_MD9600)
|
||||||
case DISPLAY_BATTERY_UNIT_IN_HEADER:
|
case DISPLAY_BATTERY_UNIT_IN_HEADER:
|
||||||
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER) == false)
|
|
||||||
{
|
{
|
||||||
settingsSetOptionBit(BIT_BATTERY_VOLTAGE_IN_HEADER, true);
|
batteryUnitDisplay_t batteryUnit = settingsGetBatteryUnitDisplay();
|
||||||
|
if (batteryUnit < (NUM_BATTERY_UNIT_DISPLAY - 1))
|
||||||
|
{
|
||||||
|
settingsSetBatteryUnitDisplay(batteryUnit + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -928,9 +936,12 @@ static void handleEvent(uiEvent_t *ev)
|
||||||
|
|
||||||
#if ! defined(PLATFORM_MD9600)
|
#if ! defined(PLATFORM_MD9600)
|
||||||
case DISPLAY_BATTERY_UNIT_IN_HEADER:
|
case DISPLAY_BATTERY_UNIT_IN_HEADER:
|
||||||
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
|
|
||||||
{
|
{
|
||||||
settingsSetOptionBit(BIT_BATTERY_VOLTAGE_IN_HEADER, false);
|
batteryUnitDisplay_t batteryUnit = settingsGetBatteryUnitDisplay();
|
||||||
|
if (batteryUnit > BATTERY_UNIT_PERCENTAGE)
|
||||||
|
{
|
||||||
|
settingsSetBatteryUnitDisplay(batteryUnit - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1182,6 +1193,7 @@ static void exitCallback(void *data)
|
||||||
#endif
|
#endif
|
||||||
#if ! defined(PLATFORM_MD9600)
|
#if ! defined(PLATFORM_MD9600)
|
||||||
BIT_BATTERY_VOLTAGE_IN_HEADER,
|
BIT_BATTERY_VOLTAGE_IN_HEADER,
|
||||||
|
BIT_BATTERY_BARS_IN_HEADER,
|
||||||
#endif
|
#endif
|
||||||
#if defined(HAS_SOFT_VOLUME)
|
#if defined(HAS_SOFT_VOLUME)
|
||||||
BIT_VISUAL_VOLUME,
|
BIT_VISUAL_VOLUME,
|
||||||
|
|
|
||||||
|
|
@ -1897,7 +1897,7 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
|
||||||
UNUSED_PARAMETER(forceBatteryDisplay);
|
UNUSED_PARAMETER(forceBatteryDisplay);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
|
if (settingsGetBatteryUnitDisplay() == BATTERY_UNIT_VOLTAGE)
|
||||||
{
|
{
|
||||||
getBatteryVoltage(&volts, &mvolts);
|
getBatteryVoltage(&volts, &mvolts);
|
||||||
|
|
||||||
|
|
@ -1911,6 +1911,7 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Both the percentage and bars display modes need the battery percentage.
|
||||||
batteryPercentage = getBatteryPercentage();
|
batteryPercentage = getBatteryPercentage();
|
||||||
|
|
||||||
if (batteryPercentage < 100)
|
if (batteryPercentage < 100)
|
||||||
|
|
@ -2207,7 +2208,9 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
|
batteryUnitDisplay_t batteryUnit = settingsGetBatteryUnitDisplay();
|
||||||
|
|
||||||
|
if (batteryUnit == BATTERY_UNIT_VOLTAGE)
|
||||||
{
|
{
|
||||||
int16_t xV = (DISPLAY_SIZE_X - ((4 * 6) + 3));
|
int16_t xV = (DISPLAY_SIZE_X - ((4 * 6) + 3));
|
||||||
|
|
||||||
|
|
@ -2219,6 +2222,41 @@ void uiUtilityRenderHeader(bool isVFODualWatchScanning, bool isVFOSweepScanning,
|
||||||
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%1dV", mvolts);
|
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%1dV", mvolts);
|
||||||
displayPrintCore(xV + (6 * 2) + 3, DISPLAY_Y_POS_HEADER, buffer, (apoEnabled ? FONT_SIZE_1_BOLD : FONT_SIZE_1), TEXT_ALIGN_LEFT, ((batteryIsLow ? scanBlinkPhase : false)));
|
displayPrintCore(xV + (6 * 2) + 3, DISPLAY_Y_POS_HEADER, buffer, (apoEnabled ? FONT_SIZE_1_BOLD : FONT_SIZE_1), TEXT_ALIGN_LEFT, ((batteryIsLow ? scanBlinkPhase : false)));
|
||||||
}
|
}
|
||||||
|
else if (batteryUnit == BATTERY_UNIT_BARS)
|
||||||
|
{
|
||||||
|
// Battery-shaped icon, right-aligned in the header: an outlined body with a
|
||||||
|
// terminal nub, holding up to 4 segments each representing 25% of charge.
|
||||||
|
const int16_t bodyW = 24;
|
||||||
|
const int16_t bodyH = 7;
|
||||||
|
const int16_t nubW = 2;
|
||||||
|
const int16_t nubH = 3;
|
||||||
|
const int16_t barW = 4;
|
||||||
|
const int16_t barGap = 1;
|
||||||
|
int16_t bx = (DISPLAY_SIZE_X - bodyW - nubW);
|
||||||
|
int16_t by = DISPLAY_Y_POS_HEADER;
|
||||||
|
|
||||||
|
// 0..4 filled segments (rounded to the nearest quarter).
|
||||||
|
int16_t filledBars = ((batteryPercentage + 12) / 25);
|
||||||
|
if (filledBars > 4)
|
||||||
|
{
|
||||||
|
filledBars = 4;
|
||||||
|
}
|
||||||
|
else if (filledBars < 0)
|
||||||
|
{
|
||||||
|
filledBars = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body outline and terminal nub stay solid; only the fill blinks when low.
|
||||||
|
// NOTE: displayDrawRect uses isInverted==true for foreground (visible), whereas
|
||||||
|
// displayFillRect is the opposite (isInverted==false draws the foreground).
|
||||||
|
displayDrawRect(bx, by, bodyW, bodyH, true);
|
||||||
|
displayFillRect(bx + bodyW, by + ((bodyH - nubH) / 2), nubW, nubH, false);
|
||||||
|
|
||||||
|
for (int16_t b = 0; b < filledBars; b++)
|
||||||
|
{
|
||||||
|
displayFillRect(bx + 2 + (b * (barW + barGap)), by + 2, barW, (bodyH - 4), ((batteryIsLow ? scanBlinkPhase : false)));
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d%%", batteryPercentage);
|
snprintf(buffer, SCREEN_LINE_BUFFER_SIZE, "%d%%", batteryPercentage);
|
||||||
|
|
@ -3149,12 +3187,13 @@ void announceItemWithInit(bool init, voicePromptItem_t item, audioPromptThreshol
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PROMPT_SEQUENCE_BATTERY:
|
case PROMPT_SEQUENCE_BATTERY:
|
||||||
if (settingsIsOptionBitSet(BIT_BATTERY_VOLTAGE_IN_HEADER))
|
if (settingsGetBatteryUnitDisplay() == BATTERY_UNIT_VOLTAGE)
|
||||||
{
|
{
|
||||||
announceBatteryVoltage();
|
announceBatteryVoltage();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// Bars mode maps to a charge percentage, so announce it as a percentage.
|
||||||
announceBatteryPercentage();
|
announceBatteryPercentage();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue