Themes: selectable on-device presets (Custom / BASIC 80s / Terminal)
Add a Theme selector in Display Options that cycles three presets with a live preview: Custom (CPS-editable, default), BASIC 80s (Commodore 64 light-blue on blue) and Terminal (green-on-black night / black-on-green day). Presets fill all theme items for a cohesive look. The selection is stored in the codeplug custom-data area (new THEME_PRESET block), which is migration-safe (missing block -> Custom) and needs no settings reset. CPS theme edits only ever change the Custom theme. Documented in MANUAL.md and the README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ac77c5ae9
commit
8c9828e386
7 changed files with 166 additions and 16 deletions
|
|
@ -7,6 +7,11 @@ single running list until the first versioned release.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- **Theme presets** — selectable on the radio in Display Options → "Theme":
|
||||||
|
**Custom** (CPS-editable, default), **BASIC 80s** (Commodore 64 blue/light-blue),
|
||||||
|
and **Terminal** (green-on-black night / black-on-green day). The choice is stored
|
||||||
|
in the codeplug custom-data area (migration-safe; defaults to Custom), and CPS
|
||||||
|
theme edits only ever change Custom.
|
||||||
- **Fix:** green LED **flickering** between the red flashes while muted during a DMR
|
- **Fix:** green LED **flickering** between the red flashes while muted during a DMR
|
||||||
QSO. The DMR RX handler re-asserts the green LED as soon as it reads it off, racing
|
QSO. The DMR RX handler re-asserts the green LED as soon as it reads it off, racing
|
||||||
the main-loop mute clear; the green LED is now **held off at the driver** for the
|
the main-loop mute clear; the green LED is now **held off at the driver** for the
|
||||||
|
|
|
||||||
11
MANUAL.md
11
MANUAL.md
|
|
@ -58,6 +58,17 @@ Optionally dims the backlight after inactivity to **100 / 50 / 30 / 15 %** of th
|
||||||
configured brightness (only in **Auto** backlight mode). Any activity restores full
|
configured brightness (only in **Auto** backlight mode). Any activity restores full
|
||||||
brightness. Set it in **Menu → Options → Display Options → Screen Dim**.
|
brightness. Set it in **Menu → Options → Display Options → Screen Dim**.
|
||||||
|
|
||||||
|
### Theme presets — 🟢 [FreeTRX]
|
||||||
|
Pick a colour theme on the radio at **Menu → Options → Display Options → "Theme"**
|
||||||
|
(◀/▶ to change; the screen updates live):
|
||||||
|
|
||||||
|
- **Custom** — the theme edited via the CPS or the on-radio colour editor (default).
|
||||||
|
- **BASIC 80s** — a Commodore-64 look (light blue on blue).
|
||||||
|
- **Terminal** — a green terminal (green-on-black at night, black-on-green by day).
|
||||||
|
|
||||||
|
Editing colours in the CPS only affects **Custom**; the two built-in presets are
|
||||||
|
fixed. Every element (S-meter, frequencies, menus) follows the selected theme.
|
||||||
|
|
||||||
### Main-menu icons — 🟢 [FreeTRX]
|
### Main-menu icons — 🟢 [FreeTRX]
|
||||||
Each top-level menu entry has a small icon next to it.
|
Each top-level menu entry has a small icon next to it.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -353,6 +353,7 @@ typedef enum
|
||||||
CODEPLUG_CUSTOM_DATA_TYPE_SATELLITE_TLE,
|
CODEPLUG_CUSTOM_DATA_TYPE_SATELLITE_TLE,
|
||||||
CODEPLUG_CUSTOM_DATA_TYPE_THEME_DAY,
|
CODEPLUG_CUSTOM_DATA_TYPE_THEME_DAY,
|
||||||
CODEPLUG_CUSTOM_DATA_TYPE_THEME_NIGHT,
|
CODEPLUG_CUSTOM_DATA_TYPE_THEME_NIGHT,
|
||||||
|
CODEPLUG_CUSTOM_DATA_TYPE_THEME_PRESET, // FreeTRX: 1 byte, selected theme preset (themePreset_t)
|
||||||
} CodeplugCustomDataType_t;
|
} CodeplugCustomDataType_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -269,8 +269,21 @@ void displayDrawFrequencySevenSeg(int16_t x, int16_t y, int16_t cellW, int16_t c
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(HAS_COLOURS)
|
#if defined(HAS_COLOURS)
|
||||||
|
// FreeTRX selectable theme presets. CUSTOM is the CPS-editable theme (default);
|
||||||
|
// the others are fixed palettes baked into the firmware.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
THEME_PRESET_CUSTOM = 0, // editable via CPS / on-device colour editor
|
||||||
|
THEME_PRESET_BASIC80S, // Commodore 64 BASIC (light blue on blue)
|
||||||
|
THEME_PRESET_TERMINAL, // green terminal (green-on-black night / black-on-green day)
|
||||||
|
NUM_THEME_PRESETS
|
||||||
|
} themePreset_t;
|
||||||
|
|
||||||
void themeInitToDefaultValues(DayTime_t daytime, bool invert);
|
void themeInitToDefaultValues(DayTime_t daytime, bool invert);
|
||||||
void themeInit(bool SPIFlashAvailable);
|
void themeInit(bool SPIFlashAvailable);
|
||||||
|
void themeApplyPreset(uint8_t preset); // rebuild themeItems[] for the given preset
|
||||||
|
uint8_t themeGetPreset(void); // read the stored preset (default CUSTOM)
|
||||||
|
void themeSetPreset(uint8_t preset, bool save); // apply, and optionally persist to the codeplug
|
||||||
void displayThemeApply(themeItem_t fgItem, themeItem_t bgItem);
|
void displayThemeApply(themeItem_t fgItem, themeItem_t bgItem);
|
||||||
void displayThemeResetToDefault(void);
|
void displayThemeResetToDefault(void);
|
||||||
bool displayThemeIsForegroundColourEqualTo(themeItem_t fgItem);
|
bool displayThemeIsForegroundColourEqualTo(themeItem_t fgItem);
|
||||||
|
|
|
||||||
|
|
@ -1510,29 +1510,105 @@ void themeInitToDefaultValues(DayTime_t daytime, bool invert)
|
||||||
themeItems[daytime][THEME_ITEM_FG_BD_COLOUR] = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(0xFF0000U));
|
themeItems[daytime][THEME_ITEM_FG_BD_COLOUR] = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(0xFF0000U));
|
||||||
}
|
}
|
||||||
|
|
||||||
void themeInit(bool SPIFlashAvailable)
|
// Set every theme item to fgRGB, then override the background items with bgRGB, for
|
||||||
|
// a cohesive single-palette look. The selected-menu-item background is intentionally
|
||||||
|
// left as the foreground colour so the highlighted row still inverts.
|
||||||
|
static void themeSetAllItems(DayTime_t daytime, uint32_t fgRGB, uint32_t bgRGB)
|
||||||
{
|
{
|
||||||
|
uint16_t fg = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(fgRGB));
|
||||||
|
uint16_t bg = PLATFORM_COLOUR_FORMAT_SWAP_BYTES(RGB888_TO_PLATFORM_COLOUR_FORMAT(bgRGB));
|
||||||
|
|
||||||
|
for (int i = 0; i < THEME_ITEM_MAX; i++)
|
||||||
|
{
|
||||||
|
themeItems[daytime][i] = fg;
|
||||||
|
}
|
||||||
|
themeItems[daytime][THEME_ITEM_BG] = bg;
|
||||||
|
themeItems[daytime][THEME_ITEM_BG_SPLASHSCREEN] = bg;
|
||||||
|
themeItems[daytime][THEME_ITEM_BG_NOTIFICATION] = bg;
|
||||||
|
themeItems[daytime][THEME_ITEM_BG_MENU_NAME] = bg;
|
||||||
|
themeItems[daytime][THEME_ITEM_BG_HEADER_TEXT] = bg;
|
||||||
|
// THEME_ITEM_BG_MENU_ITEM_SELECTED intentionally left = fg (inverts the selected row).
|
||||||
|
}
|
||||||
|
|
||||||
|
void themeApplyPreset(uint8_t preset)
|
||||||
|
{
|
||||||
|
// Start from the monochrome defaults, then overlay the chosen preset.
|
||||||
themeInitToDefaultValues(NIGHT, true);
|
themeInitToDefaultValues(NIGHT, true);
|
||||||
themeInitToDefaultValues(DAY, false);
|
themeInitToDefaultValues(DAY, false);
|
||||||
|
|
||||||
// Read and apply user's theme, if any
|
switch (preset)
|
||||||
if (SPIFlashAvailable)
|
{
|
||||||
|
case THEME_PRESET_BASIC80S: // Commodore 64 BASIC: light blue on blue (day & night)
|
||||||
|
themeSetAllItems(DAY, 0x7C71D5, 0x3E31A2);
|
||||||
|
themeSetAllItems(NIGHT, 0x7C71D5, 0x3E31A2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THEME_PRESET_TERMINAL:
|
||||||
|
themeSetAllItems(DAY, 0x000000, 0x00A000); // black on green
|
||||||
|
themeSetAllItems(NIGHT, 0x00E000, 0x000000); // green on black
|
||||||
|
break;
|
||||||
|
|
||||||
|
case THEME_PRESET_CUSTOM:
|
||||||
|
default:
|
||||||
|
// Overlay the user's Custom theme from the codeplug, if present (CPS edits
|
||||||
|
// only ever touch this).
|
||||||
{
|
{
|
||||||
uint16_t themingTmp[THEME_ITEM_MAX];
|
uint16_t themingTmp[THEME_ITEM_MAX];
|
||||||
|
|
||||||
if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_DAY, (uint8_t *) &themingTmp))
|
if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_DAY, (uint8_t *) &themingTmp))
|
||||||
{
|
{
|
||||||
memcpy(&themeItems[DAY], &themingTmp, sizeof(themingTmp));
|
memcpy(&themeItems[DAY], &themingTmp, sizeof(themingTmp));
|
||||||
|
|
||||||
foregroundColour = themeItems[DAY][THEME_ITEM_FG_DEFAULT];
|
|
||||||
backgroundColour = themeItems[DAY][THEME_ITEM_BG];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_NIGHT, (uint8_t *) &themingTmp))
|
if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_NIGHT, (uint8_t *) &themingTmp))
|
||||||
{
|
{
|
||||||
memcpy(&themeItems[NIGHT], &themingTmp, sizeof(themingTmp));
|
memcpy(&themeItems[NIGHT], &themingTmp, sizeof(themingTmp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foregroundColour = themeItems[DAY][THEME_ITEM_FG_DEFAULT];
|
||||||
|
backgroundColour = themeItems[DAY][THEME_ITEM_BG];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t themeGetPreset(void)
|
||||||
|
{
|
||||||
|
uint8_t buf[4] = { THEME_PRESET_CUSTOM, 0, 0, 0 };
|
||||||
|
|
||||||
|
if (codeplugGetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_PRESET, buf))
|
||||||
|
{
|
||||||
|
return (buf[0] < NUM_THEME_PRESETS) ? buf[0] : THEME_PRESET_CUSTOM;
|
||||||
|
}
|
||||||
|
return THEME_PRESET_CUSTOM;
|
||||||
|
}
|
||||||
|
|
||||||
|
void themeSetPreset(uint8_t preset, bool save)
|
||||||
|
{
|
||||||
|
if (preset >= NUM_THEME_PRESETS)
|
||||||
|
{
|
||||||
|
preset = THEME_PRESET_CUSTOM;
|
||||||
|
}
|
||||||
|
|
||||||
|
themeApplyPreset(preset);
|
||||||
|
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
uint8_t buf[1] = { preset };
|
||||||
|
codeplugSetOpenGD77CustomData(CODEPLUG_CUSTOM_DATA_TYPE_THEME_PRESET, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void themeInit(bool SPIFlashAvailable)
|
||||||
|
{
|
||||||
|
if (SPIFlashAvailable)
|
||||||
|
{
|
||||||
|
themeApplyPreset(themeGetPreset());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
themeInitToDefaultValues(NIGHT, true);
|
||||||
|
themeInitToDefaultValues(DAY, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (settingsIsOptionBitSet(BIT_AUTO_NIGHT_OVERRIDE) && (uiDataGlobal.daytimeOverridden == NIGHT))
|
if (settingsIsOptionBitSet(BIT_AUTO_NIGHT_OVERRIDE) && (uiDataGlobal.daytimeOverridden == NIGHT))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,9 @@ enum
|
||||||
DISPLAY_SHOW_DISTANCE,
|
DISPLAY_SHOW_DISTANCE,
|
||||||
DISPLAY_DMR_LAST_TALKER_ON_SCREEN,
|
DISPLAY_DMR_LAST_TALKER_ON_SCREEN,
|
||||||
DISPLAY_SCREEN_DIM,
|
DISPLAY_SCREEN_DIM,
|
||||||
|
#if defined(HAS_COLOURS)
|
||||||
|
DISPLAY_THEME_PRESET,
|
||||||
|
#endif
|
||||||
NUM_DISPLAY_MENU_ITEMS
|
NUM_DISPLAY_MENU_ITEMS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -269,6 +272,18 @@ static void updateScreen(bool isFirstRun)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#if defined(HAS_COLOURS)
|
||||||
|
case DISPLAY_THEME_PRESET:
|
||||||
|
{
|
||||||
|
// Preset names are plain literals -> rightSideVar (voice-safe).
|
||||||
|
const char *presetNames[] = { "Custom", "BASIC 80s", "Terminal" };
|
||||||
|
uint8_t p = themeGetPreset();
|
||||||
|
leftSide = "Theme";
|
||||||
|
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%s", presetNames[(p < NUM_THEME_PRESETS) ? p : 0]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ! defined(PLATFORM_GD77S)
|
#if ! defined(PLATFORM_GD77S)
|
||||||
case DISPLAY_AUTO_NIGHT:
|
case DISPLAY_AUTO_NIGHT:
|
||||||
leftSide = currentLanguage->auto_night;
|
leftSide = currentLanguage->auto_night;
|
||||||
|
|
@ -630,6 +645,19 @@ static void handleEvent(uiEvent_t *ev)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#if defined(HAS_COLOURS)
|
||||||
|
case DISPLAY_THEME_PRESET:
|
||||||
|
{
|
||||||
|
uint8_t p = themeGetPreset();
|
||||||
|
if (p < (NUM_THEME_PRESETS - 1))
|
||||||
|
{
|
||||||
|
themeSetPreset(p + 1, true); // apply + persist, live preview
|
||||||
|
displayThemeResetToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ! defined(PLATFORM_GD77S)
|
#if ! defined(PLATFORM_GD77S)
|
||||||
case DISPLAY_AUTO_NIGHT:
|
case DISPLAY_AUTO_NIGHT:
|
||||||
if (settingsIsOptionBitSet(BIT_AUTO_NIGHT) == false)
|
if (settingsIsOptionBitSet(BIT_AUTO_NIGHT) == false)
|
||||||
|
|
@ -839,6 +867,19 @@ static void handleEvent(uiEvent_t *ev)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
#if defined(HAS_COLOURS)
|
||||||
|
case DISPLAY_THEME_PRESET:
|
||||||
|
{
|
||||||
|
uint8_t p = themeGetPreset();
|
||||||
|
if (p > THEME_PRESET_CUSTOM)
|
||||||
|
{
|
||||||
|
themeSetPreset(p - 1, true); // apply + persist, live preview
|
||||||
|
displayThemeResetToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
#if ! defined(PLATFORM_GD77S)
|
#if ! defined(PLATFORM_GD77S)
|
||||||
case DISPLAY_AUTO_NIGHT:
|
case DISPLAY_AUTO_NIGHT:
|
||||||
if (settingsIsOptionBitSet(BIT_AUTO_NIGHT))
|
if (settingsIsOptionBitSet(BIT_AUTO_NIGHT))
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ FreeTRX keeps OpenGD77's core (DMR/FM, hotspot, CPS compatibility) and adds:
|
||||||
- **7-segment "LED" frequency font** on the VFO and the SK1-held frequency view.
|
- **7-segment "LED" frequency font** on the VFO and the SK1-held frequency view.
|
||||||
- **Screen dimming** — optionally dim the backlight to 100 / 50 / 30 / 15 % after
|
- **Screen dimming** — optionally dim the backlight to 100 / 50 / 30 / 15 % after
|
||||||
inactivity (Auto backlight mode); new setting in Display Options.
|
inactivity (Auto backlight mode); new setting in Display Options.
|
||||||
|
- **Theme presets** — pick a colour theme on the radio: **Custom** (CPS-editable,
|
||||||
|
default), **BASIC 80s** (Commodore 64), or **Terminal** (green-on-black). CPS edits
|
||||||
|
only affect Custom.
|
||||||
- **On-radio channel management** — create and delete channels directly from the
|
- **On-radio channel management** — create and delete channels directly from the
|
||||||
Channel quick menu, no PC/CPS required (see [Managing channels](#managing-channels-on-the-radio)).
|
Channel quick menu, no PC/CPS required (see [Managing channels](#managing-channels-on-the-radio)).
|
||||||
- **Main-menu icons** next to each top-level entry.
|
- **Main-menu icons** next to each top-level entry.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue