732 lines
22 KiB
C
732 lines
22 KiB
C
/*
|
|
* Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF
|
|
* Daniel Caujolle-Bert, F1RMB
|
|
*
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation,
|
|
* incorporation in a commercial product or incorporation into a product or project which allows commercial use.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
#include "functions/settings.h"
|
|
#include "user_interface/menuSystem.h"
|
|
#include "user_interface/uiLocalisation.h"
|
|
#include "user_interface/uiUtilities.h"
|
|
#include "interfaces/wdog.h"
|
|
#include "utils.h"
|
|
|
|
#if defined(HAS_COLOURS)
|
|
|
|
static const char *themeEntries[THEME_ITEM_MAX];
|
|
static uint16_t userThemeData[THEME_ITEM_MAX] = { 0xDEAD, 0xBEEF };
|
|
static int32_t currentThemeEntry = 0;
|
|
static DayTime_t currentThemeDaytime = UNDEFINED;
|
|
|
|
static void updateScreen(bool isFirstRun);
|
|
static void handleEvent(uiEvent_t *ev);
|
|
static void resetUserThemeData(void);
|
|
|
|
// ------
|
|
|
|
#define COLOUR_CHANNEL_UNSET 0xFFFF
|
|
#define COLOUR_RGB888_UNSET 0xDEADBEEF
|
|
|
|
typedef enum
|
|
{
|
|
COLOUR_CHANNEL_RED = 0,
|
|
COLOUR_CHANNEL_GREEN,
|
|
COLOUR_CHANNEL_BLUE,
|
|
COLOUR_CHANNEL_MAX
|
|
} colourChannel_t;
|
|
|
|
typedef struct
|
|
{
|
|
const char *name;
|
|
int32_t value; // we need to handle < 0 and 0xFFFF
|
|
} colourChannelData_t;
|
|
|
|
static colourChannelData_t colourChannelsData[COLOUR_CHANNEL_MAX] =
|
|
{
|
|
{ NULL, COLOUR_CHANNEL_UNSET },
|
|
{ NULL, COLOUR_CHANNEL_UNSET },
|
|
{ NULL, COLOUR_CHANNEL_UNSET }
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
uint16_t maxValue;
|
|
uint8_t step;
|
|
} channelConstraint_t;
|
|
|
|
static channelConstraint_t channelConstraints[COLOUR_CHANNEL_MAX] =
|
|
{
|
|
{ 248, 8 }, // RED
|
|
{ 252, 4 }, // GREEN
|
|
{ 248, 8 }, // BLUE
|
|
};
|
|
|
|
static uint32_t colourToEditRGB888 = COLOUR_RGB888_UNSET;
|
|
|
|
static void updateBrowerScreen(bool isFirstRun);
|
|
static void handleBrowserEvent(uiEvent_t *ev);
|
|
static void exitBrowserCallback(void *data);
|
|
static void updatePickerScreen(bool isFirstRun);
|
|
static void handlePickerEvent(uiEvent_t *ev);
|
|
static void exitPickerCallback(void *data);
|
|
static void resetColourChannelsData(void);
|
|
|
|
static menuStatus_t menuOptionsExitCode = MENU_STATUS_SUCCESS;
|
|
|
|
menuStatus_t menuThemeOptions(uiEvent_t *ev, bool isFirstRun)
|
|
{
|
|
if (isFirstRun)
|
|
{
|
|
currentThemeDaytime = (DayTime_t)uiDataGlobal.currentSelectedChannelNumber;
|
|
menuDataGlobal.numItems = (NIGHT + 1);
|
|
//menuDataGlobal.currentItemIndex = 0;
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendLanguageString(currentLanguage->theme_chooser);
|
|
voicePromptsAppendLanguageString(currentLanguage->menu);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
|
|
updateScreen(true);
|
|
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
menuOptionsExitCode = MENU_STATUS_SUCCESS;
|
|
|
|
if (ev->hasEvent)
|
|
{
|
|
handleEvent(ev);
|
|
}
|
|
}
|
|
|
|
return menuOptionsExitCode;
|
|
}
|
|
|
|
static void updateScreen(bool isFirstRun)
|
|
{
|
|
int mNum = 0;
|
|
char buf[SCREEN_LINE_BUFFER_SIZE];
|
|
const char *leftSide = NULL;// initialize to please the compiler
|
|
|
|
displayClearBuf();
|
|
menuDisplayTitle(currentLanguage->theme_chooser);
|
|
|
|
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
|
{
|
|
if (menuDataGlobal.numItems <= (i + 1))
|
|
{
|
|
break;
|
|
}
|
|
|
|
mNum = menuGetMenuOffset(menuDataGlobal.numItems, i);
|
|
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
|
{
|
|
continue;
|
|
}
|
|
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
|
{
|
|
break;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
buf[2] = 0;
|
|
leftSide = NULL;
|
|
|
|
switch(mNum)
|
|
{
|
|
default:
|
|
leftSide = (const char *)((((DayTime_t)mNum) == DAY) ? currentLanguage->daytime_theme_day : currentLanguage->daytime_theme_night);
|
|
break;
|
|
}
|
|
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s", leftSide);
|
|
|
|
if (i == 0)
|
|
{
|
|
bool wasPlaying = voicePromptsIsPlaying();
|
|
|
|
if (!isFirstRun)
|
|
{
|
|
voicePromptsInit();
|
|
}
|
|
|
|
if (!wasPlaying || (menuDataGlobal.newOptionSelected || (menuDataGlobal.menuOptionsTimeout > 0)))
|
|
{
|
|
voicePromptsAppendLanguageString(leftSide);
|
|
}
|
|
|
|
promptsPlayNotAfterTx();
|
|
}
|
|
|
|
menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG);
|
|
}
|
|
|
|
displayRender();
|
|
}
|
|
|
|
static void handleEvent(uiEvent_t *ev)
|
|
{
|
|
bool isDirty = false;
|
|
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (repeatVoicePromptOnSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ev->events & FUNCTION_EVENT)
|
|
{
|
|
if (ev->function == FUNC_REDRAW)
|
|
{
|
|
isDirty = true;
|
|
//updateScreen(false);
|
|
//return;
|
|
}
|
|
else if ((QUICKKEY_TYPE(ev->function) == QUICKKEY_MENU) && (QUICKKEY_ENTRYID(ev->function) < (NIGHT + 1)))
|
|
{
|
|
isDirty = true;
|
|
menuDataGlobal.currentItemIndex = QUICKKEY_ENTRYID(ev->function);
|
|
//updateScreen(false);
|
|
}
|
|
}
|
|
|
|
if (ev->events & KEY_EVENT)
|
|
{
|
|
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (menuDataGlobal.numItems != 0))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, (NIGHT + 1));
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuOptionsExitCode |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, (NIGHT + 1));
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuOptionsExitCode |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
|
{
|
|
currentThemeDaytime = (DayTime_t)menuDataGlobal.currentItemIndex;
|
|
menuSystemPushNewMenu(MENU_THEME_ITEMS_BROWSER);
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
|
{
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP_NUMBER(ev->keys) && BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
saveQuickkeyMenuIndex(ev->keys.key, menuSystemGetCurrentMenuNumber(), menuDataGlobal.currentItemIndex, 0);
|
|
isDirty = true;
|
|
}
|
|
}
|
|
|
|
if (isDirty)
|
|
{
|
|
updateScreen(false);
|
|
}
|
|
}
|
|
|
|
menuStatus_t menuThemeItemsBrowser(uiEvent_t *ev, bool isFirstRun)
|
|
{
|
|
if (isFirstRun)
|
|
{
|
|
menuDataGlobal.menuOptionsSetQuickkey = 0;
|
|
menuDataGlobal.menuOptionsTimeout = 0;
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuDataGlobal.numItems = THEME_ITEM_MAX;
|
|
|
|
themeEntries[THEME_ITEM_FG_DEFAULT] = currentLanguage->theme_fg_default;
|
|
themeEntries[THEME_ITEM_BG] = currentLanguage->theme_bg;
|
|
themeEntries[THEME_ITEM_FG_DECORATION] = currentLanguage->theme_fg_decoration;
|
|
themeEntries[THEME_ITEM_FG_TEXT_INPUT] = currentLanguage->theme_fg_text_input;
|
|
themeEntries[THEME_ITEM_FG_SPLASHSCREEN] = currentLanguage->theme_fg_splashscreen;
|
|
themeEntries[THEME_ITEM_BG_SPLASHSCREEN] = currentLanguage->theme_bg_splashscreen;
|
|
themeEntries[THEME_ITEM_FG_NOTIFICATION] = currentLanguage->theme_fg_notification;
|
|
themeEntries[THEME_ITEM_FG_WARNING_NOTIFICATION] = currentLanguage->theme_fg_warning_notification;
|
|
themeEntries[THEME_ITEM_FG_ERROR_NOTIFICATION] = currentLanguage->theme_fg_error_notification;
|
|
themeEntries[THEME_ITEM_BG_NOTIFICATION] = currentLanguage->theme_bg_notification;
|
|
themeEntries[THEME_ITEM_FG_MENU_NAME] = currentLanguage->theme_fg_menu_name;
|
|
themeEntries[THEME_ITEM_BG_MENU_NAME] = currentLanguage->theme_bg_menu_name;
|
|
themeEntries[THEME_ITEM_FG_MENU_ITEM] = currentLanguage->theme_fg_menu_item;
|
|
themeEntries[THEME_ITEM_BG_MENU_ITEM_SELECTED] = currentLanguage->theme_fg_menu_item_selected;
|
|
themeEntries[THEME_ITEM_FG_OPTIONS_VALUE] = currentLanguage->theme_fg_options_value;
|
|
themeEntries[THEME_ITEM_FG_HEADER_TEXT] = currentLanguage->theme_fg_header_text;
|
|
themeEntries[THEME_ITEM_BG_HEADER_TEXT] = currentLanguage->theme_bg_header_text;
|
|
themeEntries[THEME_ITEM_FG_RSSI_BAR] = currentLanguage->theme_fg_rssi_bar;
|
|
themeEntries[THEME_ITEM_FG_RSSI_BAR_S9P] = currentLanguage->theme_fg_rssi_bar_s9p;
|
|
themeEntries[THEME_ITEM_FG_CHANNEL_NAME] = currentLanguage->theme_fg_channel_name;
|
|
themeEntries[THEME_ITEM_FG_CHANNEL_CONTACT] = currentLanguage->theme_fg_channel_contact;
|
|
themeEntries[THEME_ITEM_FG_CHANNEL_CONTACT_INFO] = currentLanguage->theme_fg_channel_contact_info;
|
|
themeEntries[THEME_ITEM_FG_ZONE_NAME] = currentLanguage->theme_fg_zone_name;
|
|
themeEntries[THEME_ITEM_FG_RX_FREQ] = currentLanguage->theme_fg_rx_freq;
|
|
themeEntries[THEME_ITEM_FG_TX_FREQ] = currentLanguage->theme_fg_tx_freq;
|
|
themeEntries[THEME_ITEM_FG_CSS_SQL_VALUES] = currentLanguage->theme_fg_css_sql_values;
|
|
themeEntries[THEME_ITEM_FG_TX_COUNTER] = currentLanguage->theme_fg_tx_counter;
|
|
themeEntries[THEME_ITEM_FG_POLAR_DRAWING] = currentLanguage->theme_fg_polar_drawing;
|
|
themeEntries[THEME_ITEM_FG_SATELLITE_COLOUR] = currentLanguage->theme_fg_satellite_colour;
|
|
themeEntries[THEME_ITEM_FG_GPS_NUMBER] = currentLanguage->theme_fg_gps_number;
|
|
themeEntries[THEME_ITEM_FG_GPS_COLOUR] = currentLanguage->theme_fg_gps_colour;
|
|
themeEntries[THEME_ITEM_FG_BD_COLOUR] = currentLanguage->theme_fg_bd_colour;
|
|
|
|
// Has to make a working copy of the global theme
|
|
if ((userThemeData[0] == 0xDEAD) && (userThemeData[1] == 0xBEEF))
|
|
{
|
|
memcpy(&userThemeData, &themeItems[currentThemeDaytime], (sizeof(uint16_t) * THEME_ITEM_MAX));
|
|
}
|
|
else
|
|
{
|
|
// Apply the edited colour to the edited theme entry
|
|
if (colourToEditRGB888 != COLOUR_RGB888_UNSET)
|
|
{
|
|
userThemeData[menuDataGlobal.currentItemIndex] = displayConvertRGB888ToNative(colourToEditRGB888);
|
|
colourToEditRGB888 = COLOUR_RGB888_UNSET;
|
|
}
|
|
}
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendLanguageString(currentLanguage->theme_options);
|
|
voicePromptsAppendLanguageString(currentLanguage->menu);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
|
|
menuSystemRegisterExitCallback(exitBrowserCallback, NULL);
|
|
|
|
updateBrowerScreen(true);
|
|
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
menuOptionsExitCode = MENU_STATUS_SUCCESS;
|
|
|
|
if (ev->hasEvent)
|
|
{
|
|
handleBrowserEvent(ev);
|
|
}
|
|
}
|
|
return menuOptionsExitCode;
|
|
}
|
|
|
|
static void updateBrowerScreen(bool isFirstRun)
|
|
{
|
|
int mNum = 0;
|
|
char buf[SCREEN_LINE_BUFFER_SIZE];
|
|
const char *leftSide = NULL;// initialize to please the compiler
|
|
|
|
displayClearBuf();
|
|
menuDisplayTitle(currentLanguage->theme_options);
|
|
|
|
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
|
{
|
|
mNum = menuGetMenuOffset(THEME_ITEM_MAX, i);
|
|
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
|
{
|
|
continue;
|
|
}
|
|
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
|
{
|
|
break;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
buf[2] = 0;
|
|
leftSide = NULL;
|
|
|
|
switch(mNum)
|
|
{
|
|
default:
|
|
leftSide = themeEntries[mNum];
|
|
break;
|
|
}
|
|
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s", leftSide);
|
|
|
|
if (i == 0)
|
|
{
|
|
bool wasPlaying = voicePromptsIsPlaying();
|
|
|
|
if (!isFirstRun)
|
|
{
|
|
voicePromptsInit();
|
|
}
|
|
|
|
if (!wasPlaying || (menuDataGlobal.newOptionSelected || (menuDataGlobal.menuOptionsTimeout > 0)))
|
|
{
|
|
voicePromptsAppendLanguageString(leftSide);
|
|
}
|
|
|
|
promptsPlayNotAfterTx();
|
|
}
|
|
|
|
menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG);
|
|
|
|
// Draw the color rectangle
|
|
int16_t x = (DISPLAY_X_POS_MENU_TEXT_OFFSET + (16 * 8) + 2);
|
|
int16_t y = DISPLAY_Y_POS_MENU_ENTRY_HIGHLIGHT + (i * MENU_ENTRY_HEIGHT) + 2;
|
|
uint16_t fgColour, bgColour;
|
|
|
|
displayGetForegroundAndBackgroundColours(&fgColour, &bgColour);
|
|
displaySetForegroundAndBackgroundColours(userThemeData[mNum], themeItems[DAYTIME_CURRENT][THEME_ITEM_BG]);
|
|
displayFillRect(x, y, 14, 12, false);
|
|
|
|
// Outline the color rectangle when it's equal to background or menu focused colour
|
|
if (((i != 0) && (bgColour == userThemeData[mNum])) ||
|
|
((i == 0) && (userThemeData[mNum] == themeItems[DAYTIME_CURRENT][THEME_ITEM_BG_MENU_ITEM_SELECTED])))
|
|
{
|
|
uint32_t invertedColour = (0xFFFFFF - __builtin_bswap16(PLATFORM_COLOUR_FORMAT_TO_RGB888(userThemeData[mNum])));
|
|
|
|
displaySetForegroundAndBackgroundColours(displayConvertRGB888ToNative(invertedColour), themeItems[DAYTIME_CURRENT][THEME_ITEM_BG_MENU_ITEM_SELECTED]);
|
|
displayDrawRect(x, y, 14, 12, true);
|
|
}
|
|
|
|
displayThemeResetToDefault();
|
|
}
|
|
|
|
displayRender();
|
|
}
|
|
|
|
static void handleBrowserEvent(uiEvent_t *ev)
|
|
{
|
|
bool isDirty = false;
|
|
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (repeatVoicePromptOnSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW))
|
|
{
|
|
updateBrowerScreen(false);
|
|
return;
|
|
}
|
|
|
|
if (ev->events & KEY_EVENT)
|
|
{
|
|
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (menuDataGlobal.numItems != 0))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, THEME_ITEM_MAX);
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuOptionsExitCode |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, THEME_ITEM_MAX);
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuOptionsExitCode |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
|
{
|
|
// User ask to go back to the default (hardcoded) theme
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK1))
|
|
{
|
|
themeInitToDefaultValues(currentThemeDaytime, ((currentThemeDaytime == DAY) ? false : true));
|
|
}
|
|
else
|
|
{
|
|
// Apply edited theme
|
|
memcpy(&themeItems[currentThemeDaytime], &userThemeData, (sizeof(uint16_t) * THEME_ITEM_MAX));
|
|
}
|
|
|
|
// Save user's theme to flash, as a CustomData block
|
|
if (BUTTONCHECK_DOWN(ev, BUTTON_SK2))
|
|
{
|
|
displayThemeSaveToFlash(currentThemeDaytime);
|
|
}
|
|
|
|
displayThemeResetToDefault();
|
|
menuSystemPopAllAndDisplayRootMenu();
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
|
{
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_HASH))
|
|
{
|
|
colourToEditRGB888 = PLATFORM_COLOUR_FORMAT_TO_RGB888(__builtin_bswap16(userThemeData[menuDataGlobal.currentItemIndex]));
|
|
currentThemeEntry = menuDataGlobal.currentItemIndex;
|
|
menuSystemPushNewMenu(MENU_COLOUR_PICKER);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (isDirty)
|
|
{
|
|
updateBrowerScreen(false);
|
|
}
|
|
}
|
|
|
|
static void exitBrowserCallback(void *data)
|
|
{
|
|
resetUserThemeData();
|
|
}
|
|
|
|
//
|
|
// Colour Picker
|
|
//
|
|
menuStatus_t menuColourPicker(uiEvent_t *ev, bool isFirstRun)
|
|
{
|
|
if (isFirstRun)
|
|
{
|
|
menuDataGlobal.menuOptionsSetQuickkey = 0;
|
|
menuDataGlobal.menuOptionsTimeout = 0;
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuDataGlobal.numItems = COLOUR_CHANNEL_MAX;
|
|
|
|
if ((colourChannelsData[COLOUR_CHANNEL_RED].value == COLOUR_CHANNEL_UNSET) &&
|
|
(colourChannelsData[COLOUR_CHANNEL_GREEN].value == COLOUR_CHANNEL_UNSET) &&
|
|
(colourChannelsData[COLOUR_CHANNEL_BLUE].value == COLOUR_CHANNEL_UNSET))
|
|
{
|
|
colourChannelsData[COLOUR_CHANNEL_RED].name = currentLanguage->theme_colour_picker_red;
|
|
colourChannelsData[COLOUR_CHANNEL_RED].value = (colourToEditRGB888 & 0xFF0000) >> 16;
|
|
|
|
colourChannelsData[COLOUR_CHANNEL_GREEN].name = currentLanguage->theme_colour_picker_green;
|
|
colourChannelsData[COLOUR_CHANNEL_GREEN].value = (colourToEditRGB888 & 0xFF00) >> 8;
|
|
|
|
colourChannelsData[COLOUR_CHANNEL_BLUE].name = currentLanguage->theme_colour_picker_blue;
|
|
colourChannelsData[COLOUR_CHANNEL_BLUE].value = (colourToEditRGB888 & 0xFF);
|
|
}
|
|
|
|
voicePromptsInit();
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
voicePromptsAppendLanguageString(currentLanguage->theme_options);
|
|
voicePromptsAppendLanguageString(themeEntries[currentThemeEntry]);
|
|
voicePromptsAppendPrompt(PROMPT_SILENCE);
|
|
|
|
menuSystemRegisterExitCallback(exitPickerCallback, NULL);
|
|
|
|
updatePickerScreen(true);
|
|
return (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS);
|
|
}
|
|
else
|
|
{
|
|
menuOptionsExitCode = MENU_STATUS_SUCCESS;
|
|
|
|
if (ev->hasEvent)
|
|
{
|
|
handlePickerEvent(ev);
|
|
}
|
|
}
|
|
|
|
return menuOptionsExitCode;
|
|
}
|
|
|
|
static uint32_t getColourInEditionRGB888(void)
|
|
{
|
|
return ((colourChannelsData[COLOUR_CHANNEL_RED].value << 16) | (colourChannelsData[COLOUR_CHANNEL_GREEN].value << 8) | colourChannelsData[COLOUR_CHANNEL_BLUE].value);
|
|
}
|
|
|
|
static void updatePickerScreen(bool isFirstRun)
|
|
{
|
|
int mNum = 0;
|
|
char buf[SCREEN_LINE_BUFFER_SIZE];
|
|
const char *leftSide = NULL;// initialize to please the compiler
|
|
char rightSideVar[SCREEN_LINE_BUFFER_SIZE];
|
|
|
|
displayClearBuf();
|
|
menuDisplayTitle(themeEntries[currentThemeEntry]);
|
|
|
|
for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++)
|
|
{
|
|
mNum = menuGetMenuOffset(COLOUR_CHANNEL_MAX, i);
|
|
if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY)
|
|
{
|
|
continue;
|
|
}
|
|
else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY)
|
|
{
|
|
break;
|
|
}
|
|
|
|
buf[0] = 0;
|
|
buf[2] = 0;
|
|
leftSide = NULL;
|
|
rightSideVar[0] = 0;
|
|
|
|
switch(mNum)
|
|
{
|
|
default:
|
|
leftSide = colourChannelsData[mNum].name;
|
|
snprintf(rightSideVar, SCREEN_LINE_BUFFER_SIZE, "%d",
|
|
(settingsIsOptionBitSet(BIT_INVERSE_VIDEO) ? (channelConstraints[mNum].maxValue - colourChannelsData[mNum].value) : colourChannelsData[mNum].value));
|
|
break;
|
|
}
|
|
|
|
snprintf(buf, SCREEN_LINE_BUFFER_SIZE, "%s:%s", leftSide, rightSideVar);
|
|
|
|
if (i == 0)
|
|
{
|
|
bool wasPlaying = voicePromptsIsPlaying();
|
|
|
|
if (!isFirstRun)
|
|
{
|
|
voicePromptsInit();
|
|
}
|
|
|
|
if (!wasPlaying || (menuDataGlobal.newOptionSelected || (menuDataGlobal.menuOptionsTimeout > 0)))
|
|
{
|
|
voicePromptsAppendLanguageString(leftSide);
|
|
}
|
|
|
|
voicePromptsAppendString(rightSideVar);
|
|
promptsPlayNotAfterTx();
|
|
}
|
|
|
|
// We're cheating the menu vertical position, moving it to the bottom of the screen.
|
|
menuDisplayEntry((i + 2), mNum, buf, (strlen(leftSide) + 1), THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_FG_OPTIONS_VALUE, THEME_ITEM_BG);
|
|
|
|
// Display color sample.
|
|
uint32_t invertedColour = (0xFFFFFF - __builtin_bswap16(PLATFORM_COLOUR_FORMAT_TO_RGB888(userThemeData[THEME_ITEM_BG])));
|
|
|
|
displaySetForegroundAndBackgroundColours(displayConvertRGB888ToNative(getColourInEditionRGB888()), themeItems[DAYTIME_CURRENT][THEME_ITEM_BG]);
|
|
displayFillRect(20, 25, (DISPLAY_SIZE_X - 40), 48, false);
|
|
|
|
displaySetForegroundAndBackgroundColours(displayConvertRGB888ToNative(invertedColour), themeItems[DAYTIME_CURRENT][THEME_ITEM_BG]);
|
|
displayDrawRect(20, 25, (DISPLAY_SIZE_X - 40), 48, true);
|
|
displayDrawRect(21, 26, (DISPLAY_SIZE_X - 40) - 2, 46, true);
|
|
|
|
displayThemeResetToDefault();
|
|
}
|
|
|
|
displayRender();
|
|
}
|
|
|
|
static void handlePickerEvent(uiEvent_t *ev)
|
|
{
|
|
bool isDirty = false;
|
|
bool isInverted = settingsIsOptionBitSet(BIT_INVERSE_VIDEO);
|
|
|
|
if (ev->events & BUTTON_EVENT)
|
|
{
|
|
if (repeatVoicePromptOnSK1(ev))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW))
|
|
{
|
|
isDirty = true;
|
|
}
|
|
|
|
if (ev->events & KEY_EVENT)
|
|
{
|
|
if (KEYCHECK_PRESS(ev->keys, KEY_DOWN) && (menuDataGlobal.numItems != 0))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, COLOUR_CHANNEL_MAX);
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuOptionsExitCode |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, KEY_UP))
|
|
{
|
|
isDirty = true;
|
|
menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, COLOUR_CHANNEL_MAX);
|
|
menuDataGlobal.newOptionSelected = true;
|
|
menuOptionsExitCode |= MENU_STATUS_LIST_TYPE;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN))
|
|
{
|
|
// Build New Colour
|
|
colourToEditRGB888 = getColourInEditionRGB888();
|
|
resetColourChannelsData();
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED))
|
|
{
|
|
menuSystemPopPreviousMenu();
|
|
return;
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, (isInverted ? KEY_LEFT : KEY_RIGHT))
|
|
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
|| KEYCHECK_SHORTUP(ev->keys, (isInverted ? KEY_ROTARY_DECREMENT : KEY_ROTARY_INCREMENT))
|
|
#endif
|
|
)
|
|
{
|
|
isDirty = true;
|
|
colourChannelsData[menuDataGlobal.currentItemIndex].value += (channelConstraints[menuDataGlobal.currentItemIndex].step * (BUTTONCHECK_DOWN(ev, BUTTON_SK2) ? 3 : 1));
|
|
|
|
if (colourChannelsData[menuDataGlobal.currentItemIndex].value > channelConstraints[menuDataGlobal.currentItemIndex].maxValue)
|
|
{
|
|
colourChannelsData[menuDataGlobal.currentItemIndex].value = channelConstraints[menuDataGlobal.currentItemIndex].maxValue;
|
|
}
|
|
}
|
|
else if (KEYCHECK_PRESS(ev->keys, (isInverted ? KEY_RIGHT : KEY_LEFT))
|
|
#if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)
|
|
|| KEYCHECK_SHORTUP(ev->keys, (isInverted ? KEY_ROTARY_INCREMENT : KEY_ROTARY_DECREMENT))
|
|
#endif
|
|
)
|
|
{
|
|
isDirty = true;
|
|
colourChannelsData[menuDataGlobal.currentItemIndex].value -= (channelConstraints[menuDataGlobal.currentItemIndex].step * (BUTTONCHECK_DOWN(ev, BUTTON_SK2) ? 3 : 1));
|
|
|
|
if (colourChannelsData[menuDataGlobal.currentItemIndex].value < 0)
|
|
{
|
|
colourChannelsData[menuDataGlobal.currentItemIndex].value = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isDirty)
|
|
{
|
|
updatePickerScreen(false);
|
|
}
|
|
}
|
|
|
|
static void exitPickerCallback(void *data)
|
|
{
|
|
if ((colourChannelsData[COLOUR_CHANNEL_RED].value != COLOUR_CHANNEL_UNSET) &&
|
|
(colourChannelsData[COLOUR_CHANNEL_GREEN].value != COLOUR_CHANNEL_UNSET) &&
|
|
(colourChannelsData[COLOUR_CHANNEL_BLUE].value != COLOUR_CHANNEL_UNSET))
|
|
{
|
|
colourToEditRGB888 = COLOUR_RGB888_UNSET;
|
|
resetColourChannelsData();
|
|
}
|
|
}
|
|
|
|
static void resetUserThemeData(void)
|
|
{
|
|
userThemeData[0] = 0xDEAD;
|
|
userThemeData[1] = 0xBEEF;
|
|
}
|
|
|
|
static void resetColourChannelsData(void)
|
|
{
|
|
colourChannelsData[COLOUR_CHANNEL_RED].value = colourChannelsData[COLOUR_CHANNEL_GREEN].value = colourChannelsData[COLOUR_CHANNEL_BLUE].value = COLOUR_CHANNEL_UNSET;
|
|
}
|
|
#endif // HAS_COLOURS
|
|
|