Compare commits

..

16 Commits

Author SHA1 Message Date
Jason P
a270216e1e Merge branch 'develop' into baseui_nodeactionsoverhaul 2026-01-14 21:21:03 -06:00
Jason P
aad8b6adce Testing unveiled some issues - fixed with these changes 2026-01-14 10:48:12 -06:00
Jason P
ac23271c86 Code cleanup 2026-01-14 10:25:05 -06:00
Jason P
777d6467dd Correct menu wording and adjust vertical divider on Node List 2026-01-14 10:14:33 -06:00
Jason P
982b7980f3 Merge branch 'develop' into baseui_nodeactionsoverhaul 2026-01-13 07:33:20 -06:00
Jason P
ac7e399919 Merge branch 'develop' into baseui_nodeactionsoverhaul 2026-01-12 19:44:08 -06:00
Jason P
f945eb38bf Improve strikethrough for columns 2026-01-12 19:30:19 -06:00
Jason P
2b69fa1044 Cross out Mute or Ignored in lists, add Save to NodeDB on changes 2026-01-12 18:36:56 -06:00
Jason P
8d4a75faeb Clean up CoPilot actions 2026-01-12 17:46:16 -06:00
Jason P
45f6f8b1a8 Merge branch 'develop' into baseui_nodeactionsoverhaul 2026-01-12 17:10:24 -06:00
Jason P
9cccf0240d CoPilot to the rescue, wired up some function in both directions 2026-01-12 12:12:41 -06:00
Jason P
888668b480 Remove addFavoritesMenu 2026-01-12 11:40:00 -06:00
Jason P
a629c43aaf Merge branch 'develop' into baseui_nodeactionsoverhaul 2026-01-12 11:35:10 -06:00
Jason P
b0c130562d Remove old favorites menu 2026-01-12 11:32:40 -06:00
Jason P
974cb8ccad Wired up commands - still a lot of work and testing 2026-01-12 11:32:00 -06:00
Jason P
58cb7f2617 Start overhaul and clean up of the Node Actions menu 2026-01-12 11:01:06 -06:00
17 changed files with 345 additions and 1380 deletions

View File

@@ -59,6 +59,7 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp
} // namespace
menuHandler::screenMenus menuHandler::menuQueue = menu_none;
uint32_t menuHandler::pickedNodeNum = 0;
bool test_enabled = false;
uint8_t test_count = 0;
@@ -1213,20 +1214,13 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
enum optionsNumbers { Back, NodePicker, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Add Favorite";
optionsEnumArray[options++] = Favorite;
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Key Verification";
optionsEnumArray[options++] = Verify;
}
optionsArray[options] = "Node Actions / Settings";
optionsEnumArray[options++] = NodePicker;
if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Show Long/Short Name";
@@ -1241,18 +1235,12 @@ void menuHandler::nodeListMenu()
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
menuQueue = add_favorite;
screen->runNow();
} else if (selected == Verify) {
menuQueue = key_verification_init;
if (selected == NodePicker) {
menuQueue = NodePicker_menu;
screen->runNow();
} else if (selected == Reset) {
menuQueue = reset_node_db_menu;
screen->runNow();
} else if (selected == TraceRoute) {
menuQueue = trace_route_menu;
screen->runNow();
} else if (selected == NodeNameLength) {
menuHandler::menuQueue = menuHandler::node_name_length_menu;
screen->runNow();
@@ -1261,6 +1249,159 @@ void menuHandler::nodeListMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::NodePicker()
{
const char *NODE_PICKER_TITLE;
if (currentResolution == ScreenResolution::UltraLow) {
NODE_PICKER_TITLE = "Pick Node";
} else {
NODE_PICKER_TITLE = "Pick A Node";
}
screen->showNodePicker(NODE_PICKER_TITLE, 30000, [](uint32_t nodenum) -> void {
LOG_INFO("Nodenum: %u", nodenum);
// Store the selection so the Manage Node menu knows which node to operate on
menuHandler::pickedNodeNum = nodenum;
// Keep UI favorite context in sync (used elsewhere for some node-based actions)
graphics::UIRenderer::currentFavoriteNodeNum = nodenum;
menuQueue = Manage_Node_menu;
screen->runNow();
});
}
void menuHandler::ManageNodeMenu()
{
// If we don't have a node selected yet, go fast exit
auto node = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!node) {
return;
}
enum optionsNumbers { Back, Favorite, Mute, TraceRoute, KeyVerification, Ignore, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
if (node->is_favorite) {
optionsArray[options] = "Unfavorite";
} else {
optionsArray[options] = "Favorite";
}
optionsEnumArray[options++] = Favorite;
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
if (isMuted) {
optionsArray[options] = "Unmute Notifications";
} else {
optionsArray[options] = "Mute Notifications";
}
optionsEnumArray[options++] = Mute;
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
optionsArray[options] = "Key Verification";
optionsEnumArray[options++] = KeyVerification;
if (node->is_ignored) {
optionsArray[options] = "Unignore Node";
} else {
optionsArray[options] = "Ignore Node";
}
optionsEnumArray[options++] = Ignore;
BannerOverlayOptions bannerOptions;
std::string title = "";
if (node->has_user && node->user.long_name && node->user.long_name[0]) {
title += sanitizeString(node->user.long_name).substr(0, 15);
} else {
char buf[20];
snprintf(buf, sizeof(buf), "%08X", (unsigned int)node->num);
title += buf;
}
bannerOptions.message = title.c_str();
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuQueue = node_base_menu;
screen->runNow();
return;
}
if (selected == Favorite) {
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!n) {
return;
}
if (n->is_favorite) {
LOG_INFO("Removing node %08X from favorites", menuHandler::pickedNodeNum);
nodeDB->set_favorite(false, menuHandler::pickedNodeNum);
} else {
LOG_INFO("Adding node %08X to favorites", menuHandler::pickedNodeNum);
nodeDB->set_favorite(true, menuHandler::pickedNodeNum);
}
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
return;
}
if (selected == Mute) {
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!n) {
return;
}
if (n->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) {
n->bitfield &= ~NODEINFO_BITFIELD_IS_MUTED_MASK;
LOG_INFO("Unmuted node %08X", menuHandler::pickedNodeNum);
} else {
n->bitfield |= NODEINFO_BITFIELD_IS_MUTED_MASK;
LOG_INFO("Muted node %08X", menuHandler::pickedNodeNum);
}
nodeDB->notifyObservers(true);
nodeDB->saveToDisk();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
return;
}
if (selected == TraceRoute) {
LOG_INFO("Starting traceroute to %08X", menuHandler::pickedNodeNum);
if (traceRouteModule) {
traceRouteModule->startTraceRoute(menuHandler::pickedNodeNum);
}
return;
}
if (selected == KeyVerification) {
LOG_INFO("Initiating key verification with %08X", menuHandler::pickedNodeNum);
if (keyVerificationModule) {
keyVerificationModule->sendInitialRequest(menuHandler::pickedNodeNum);
}
return;
}
if (selected == Ignore) {
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!n) {
return;
}
if (n->is_ignored) {
n->is_ignored = false;
LOG_INFO("Unignoring node %08X", menuHandler::pickedNodeNum);
} else {
n->is_ignored = true;
LOG_INFO("Ignoring node %08X", menuHandler::pickedNodeNum);
}
nodeDB->notifyObservers(true);
nodeDB->saveToDisk();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
return;
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::nodeNameLengthMenu()
{
static const NodeNameOption nodeNameOptions[] = {
@@ -1289,6 +1430,7 @@ void menuHandler::nodeNameLengthMenu()
}
config.display.use_long_node_name = option.value;
saveUIConfig();
LOG_INFO("Setting names to %s", option.value ? "long" : "short");
});
@@ -1958,21 +2100,6 @@ void menuHandler::shutdownMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::addFavoriteMenu()
{
const char *NODE_PICKER_TITLE;
if (currentResolution == ScreenResolution::UltraLow) {
NODE_PICKER_TITLE = "Node Favorite";
} else {
NODE_PICKER_TITLE = "Node To Favorite";
}
screen->showNodePicker(NODE_PICKER_TITLE, 30000, [](uint32_t nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
});
}
void menuHandler::removeFavoriteMenu()
{
@@ -2484,8 +2611,11 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case shutdown_menu:
shutdownMenu();
break;
case add_favorite:
addFavoriteMenu();
case NodePicker_menu:
NodePicker();
break;
case Manage_Node_menu:
ManageNodeMenu();
break;
case remove_favorite:
removeFavoriteMenu();

View File

@@ -33,7 +33,8 @@ class menuHandler
brightness_picker,
reboot_menu,
shutdown_menu,
add_favorite,
NodePicker_menu,
Manage_Node_menu,
remove_favorite,
test_menu,
number_test,
@@ -55,6 +56,7 @@ class menuHandler
DisplayUnits
};
static screenMenus menuQueue;
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000);
@@ -90,6 +92,8 @@ class menuHandler
static void BrightnessPickerMenu();
static void rebootMenu();
static void shutdownMenu();
static void NodePicker();
static void ManageNodeMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void traceRouteMenu();
@@ -149,6 +153,7 @@ using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
using NodeNameOption = MenuOption<bool>;
using PositionMenuOption = MenuOption<int>;
using ManageNodeOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;
} // namespace graphics

View File

@@ -176,6 +176,7 @@ int calculateMaxScroll(int totalEntries, int visibleRows)
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
{
x = (currentResolution == ScreenResolution::High) ? x - 2 : (currentResolution == ScreenResolution::Low) ? x - 1 : x;
for (int y = yStart; y <= yEnd; y += 2) {
display->setPixel(x, y);
}
@@ -205,9 +206,11 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
{
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - 25;
int timeOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char timeStr[10];
uint32_t seconds = sinceLastSeen(node);
@@ -234,6 +237,13 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
int rightEdge = x + columnWidth - timeOffset;
if (timeStr[strlen(timeStr) - 1] == 'm') // Fix the fact that our fonts don't line up well all the time
@@ -253,6 +263,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int barsXOffset = columnWidth - barsOffset;
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -265,6 +276,13 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
// Draw signal strength bars
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
@@ -298,6 +316,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char distStr[10] = "";
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -358,6 +377,13 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
if (strlen(distStr) > 0) {
int offset = (currentResolution == ScreenResolution::High)
@@ -392,6 +418,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -403,6 +430,13 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
}
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,

View File

@@ -155,18 +155,6 @@ void InkHUD::LogoApplet::onShutdown()
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
}
void InkHUD::LogoApplet::onApplyingChanges()
{
bringToForeground();
textLeft = "";
textRight = "";
textTitle = "Applying changes";
fontTitle = fontSmall;
inkhud->forceUpdate(Drivers::EInk::FAST, false);
}
void InkHUD::LogoApplet::onReboot()
{
bringToForeground();

View File

@@ -26,7 +26,6 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
void onBackground() override;
void onShutdown() override;
void onReboot() override;
void onApplyingChanges();
protected:
int32_t runOnce() override;

View File

@@ -22,7 +22,6 @@ enum MenuAction {
STORE_CANNEDMESSAGE_SELECTION,
SEND_CANNEDMESSAGE,
SHUTDOWN,
BACK,
NEXT_TILE,
TOGGLE_BACKLIGHT,
TOGGLE_GPS,
@@ -37,84 +36,6 @@ enum MenuAction {
TOGGLE_NOTIFICATIONS,
TOGGLE_INVERT_COLOR,
TOGGLE_12H_CLOCK,
// Regions
SET_REGION_US,
SET_REGION_EU_868,
SET_REGION_EU_433,
SET_REGION_CN,
SET_REGION_JP,
SET_REGION_ANZ,
SET_REGION_KR,
SET_REGION_TW,
SET_REGION_RU,
SET_REGION_IN,
SET_REGION_NZ_865,
SET_REGION_TH,
SET_REGION_LORA_24,
SET_REGION_UA_433,
SET_REGION_UA_868,
SET_REGION_MY_433,
SET_REGION_MY_919,
SET_REGION_SG_923,
SET_REGION_PH_433,
SET_REGION_PH_868,
SET_REGION_PH_915,
SET_REGION_ANZ_433,
SET_REGION_KZ_433,
SET_REGION_KZ_863,
SET_REGION_NP_865,
SET_REGION_BR_902,
// Device Roles
SET_ROLE_CLIENT,
SET_ROLE_CLIENT_MUTE,
SET_ROLE_ROUTER,
SET_ROLE_REPEATER,
// Presets
SET_PRESET_LONG_SLOW,
SET_PRESET_LONG_MODERATE,
SET_PRESET_LONG_FAST,
SET_PRESET_MEDIUM_SLOW,
SET_PRESET_MEDIUM_FAST,
SET_PRESET_SHORT_SLOW,
SET_PRESET_SHORT_FAST,
SET_PRESET_SHORT_TURBO,
// Timezones
SET_TZ_US_HAWAII,
SET_TZ_US_ALASKA,
SET_TZ_US_PACIFIC,
SET_TZ_US_ARIZONA,
SET_TZ_US_MOUNTAIN,
SET_TZ_US_CENTRAL,
SET_TZ_US_EASTERN,
SET_TZ_BR_BRAZILIA,
SET_TZ_UTC,
SET_TZ_EU_WESTERN,
SET_TZ_EU_CENTRAL,
SET_TZ_EU_EASTERN,
SET_TZ_ASIA_KOLKATA,
SET_TZ_ASIA_HONG_KONG,
SET_TZ_AU_AWST,
SET_TZ_AU_ACST,
SET_TZ_AU_AEST,
SET_TZ_PACIFIC_NZ,
// Power
TOGGLE_POWER_SAVE,
CALIBRATE_ADC,
// Bluetooth
TOGGLE_BLUETOOTH,
TOGGLE_BLUETOOTH_PAIR_MODE,
// Channel
TOGGLE_CHANNEL_UPLINK,
TOGGLE_CHANNEL_DOWNLINK,
TOGGLE_CHANNEL_POSITION,
SET_CHANNEL_PRECISION,
// Display
TOGGLE_DISPLAY_UNITS,
// Network
TOGGLE_WIFI,
// Administration
RESET_NODEDB_ALL,
RESET_NODEDB_KEEP_FAVORITES,
};
} // namespace NicheGraphics::InkHUD

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void onRender() override;
void show(Tile *t); // Open the menu, onto a user tile
void setStartPage(MenuPage page);
protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
@@ -57,7 +56,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
MenuPage startPageOverride = MenuPage::ROOT;
MenuPage currentPage = MenuPage::ROOT;
MenuPage previousPage = MenuPage::EXIT;
uint8_t cursor = 0; // Which menu item is currently highlighted
@@ -65,15 +63,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
uint16_t systemInfoPanelHeight = 0; // Need to know before we render
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel)
bool channelPositionEnabled = false;
bool gpsEnabled = false;
// Recents menu checkbox state (derived from settings.recentlyActiveSeconds)
static constexpr uint8_t RECENTS_COUNT = 6;
bool recentsSelected[RECENTS_COUNT] = {};
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
// Data for selecting and sending canned messages via the menu
// Placed into a sub-class for organization only

View File

@@ -30,7 +30,6 @@ class MenuItem
MenuAction action = NO_ACTION;
MenuPage nextPage = EXIT;
bool *checkState = nullptr;
bool isHeader = false; // Non-selectable section label
// Various constructors, depending on the intended function of the item
@@ -41,12 +40,6 @@ class MenuItem
: label(label), action(action), nextPage(nextPage), checkState(checkState)
{
}
static MenuItem Header(const char *label)
{
MenuItem item(label, NO_ACTION, EXIT);
item.isHeader = true;
return item;
}
};
} // namespace NicheGraphics::InkHUD

View File

@@ -20,27 +20,10 @@ enum MenuPage : uint8_t {
SEND,
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
OPTIONS,
NODE_CONFIG,
NODE_CONFIG_LORA,
NODE_CONFIG_CHANNELS, // List of channels
NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options
NODE_CONFIG_CHANNEL_PRECISION,
NODE_CONFIG_PRESET,
NODE_CONFIG_DEVICE,
NODE_CONFIG_DEVICE_ROLE,
NODE_CONFIG_POWER,
NODE_CONFIG_POWER_ADC_CAL,
NODE_CONFIG_NETWORK,
NODE_CONFIG_DISPLAY,
NODE_CONFIG_BLUETOOTH,
NODE_CONFIG_POSITION,
NODE_CONFIG_ADMIN_RESET,
TIMEZONE,
APPLETS,
AUTOSHOW,
RECENTS, // Select length of "recentlyActiveSeconds"
REGION,
EXIT, // Dismiss the menu applet
EXIT, // Dismiss the menu applet
};
} // namespace NicheGraphics::InkHUD

View File

@@ -10,37 +10,34 @@ using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet()
{
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
bool showTutorialTips = (settings->tips.firstBoot || needsRegion);
// Decide which tips (if any) should be shown to user after the boot screen
// Welcome screen
if (showTutorialTips)
if (settings->tips.firstBoot)
tipQueue.push_back(Tip::WELCOME);
// Finish setup
if (needsRegion)
// Antenna, region, timezone
// Shown at boot if region not yet set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
tipQueue.push_back(Tip::FINISH_SETUP);
// Using the UI
if (showTutorialTips) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Shutdown info
// Shown until user performs one valid shutdown
if (!settings->tips.safeShutdownSeen)
tipQueue.push_back(Tip::SAFE_SHUTDOWN);
// Using the UI
if (settings->tips.firstBoot) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Catch an incorrect attempt at rotating display
if (config.display.flip_screen)
tipQueue.push_back(Tip::ROTATION);
// Region picker
if (needsRegion)
tipQueue.push_back(Tip::PICK_REGION);
// Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
// LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
if (!tipQueue.empty())
bringToForeground();
}
@@ -54,109 +51,81 @@ void InkHUD::TipsApplet::onRender()
case Tip::FINISH_SETUP: {
setFont(fontMedium);
const char *title = "Tip: Finish Setup";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
printAt(0, 0, "Tip: Finish Setup");
setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight();
int16_t cursorY = fontMedium.lineHeight() * 1.5;
printAt(0, cursorY, "- connect antenna");
auto drawBullet = [&](const char *text) {
uint16_t bh = getWrappedTextHeight(0, width(), text);
printWrapped(0, cursorY, width(), text);
cursorY += bh + (fontSmall.lineHeight() / 3);
};
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- connect a client app");
drawBullet("- connect antenna");
drawBullet("- connect a client app");
// Only if region not set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set region");
}
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
drawBullet("- set region");
// Only if tz not set
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set timezone");
}
if (!(*config.device.tzdef && config.device.tzdef[0] != 0))
drawBullet("- set timezone");
cursorY += fontSmall.lineHeight() / 2;
drawBullet("More info at meshtastic.org");
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "More info at meshtastic.org");
setFont(fontSmall);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::PICK_REGION: {
setFont(fontMedium);
printAt(0, 0, "Set Region");
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup.");
printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM);
} break;
case Tip::SAFE_SHUTDOWN: {
setFont(fontMedium);
const char *title = "Tip: Shutdown";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
printAt(0, 0, "Tip: Shutdown");
setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight();
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n"
"This ensures data is saved.";
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
std::string shutdown;
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
shutdown += "\n";
shutdown += "This ensures data is saved.";
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::CUSTOMIZATION: {
setFont(fontMedium);
const char *title = "Tip: Customization";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
printAt(0, 0, "Tip: Customization");
setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight();
const char *body = "Configure & control display with the InkHUD menu. "
"Optional features, layout, rotation, and more.";
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
case Tip::BUTTONS: {
setFont(fontMedium);
const char *title = "Tip: Buttons";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
printAt(0, 0, "Tip: Buttons");
setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight();
auto drawBullet = [&](const char *text) {
uint16_t bh = getWrappedTextHeight(0, width(), text);
printWrapped(0, cursorY, width(), text);
cursorY += bh + (fontSmall.lineHeight() / 3);
};
int16_t cursorY = fontMedium.lineHeight() * 1.5;
if (!settings->joystick.enabled) {
drawBullet("User Button");
drawBullet("- short press: next");
drawBullet("- long press: select or open menu");
printAt(0, cursorY, "User Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- short press: next");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- long press: select / open menu");
} else {
drawBullet("Joystick");
drawBullet("- press: open menu or select");
drawBullet("Exit Button");
drawBullet("- press: switch tile or close menu");
printAt(0, cursorY, "Joystick");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- open menu / select");
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "Exit Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- switch tile / close menu");
}
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
@@ -164,21 +133,12 @@ void InkHUD::TipsApplet::onRender()
case Tip::ROTATION: {
setFont(fontMedium);
const char *title = "Tip: Rotation";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
printAt(0, 0, "Tip: Rotation");
setFont(fontSmall);
if (!settings->joystick.enabled) {
int16_t cursorY = h + fontSmall.lineHeight();
const char *body = "To rotate the display, use the InkHUD menu. "
"Long-press the user button > Options > Rotate.";
uint16_t bh = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bh + (fontSmall.lineHeight() / 2);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
} else {
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
@@ -199,15 +159,12 @@ void InkHUD::TipsApplet::renderWelcome()
{
uint16_t padW = X(0.05);
// Detect portrait orientation
bool portrait = height() > width();
// Block 1 - logo & title
// ========================
// Logo size
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3);
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3);
uint16_t logoWLimit = X(0.3);
uint16_t logoHLimit = Y(0.3);
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
@@ -220,7 +177,7 @@ void InkHUD::TipsApplet::renderWelcome()
// Center the block
// Desired effect: equal margin from display edge for logo left and title right
int16_t block1Y = portrait ? Y(0.2) : Y(0.3);
int16_t block1Y = Y(0.3);
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
@@ -235,7 +192,7 @@ void InkHUD::TipsApplet::renderWelcome()
std::string subtitle = "InkHUD";
if (width() >= 200)
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE);
printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
// Block 3 - press to continue
// ============================
@@ -267,37 +224,26 @@ void InkHUD::TipsApplet::onBackground()
// While our SystemApplet::handleInput flag is true
void InkHUD::TipsApplet::onButtonShortPress()
{
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
// If we're prompting the user to pick a region, hand off to the menu
if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) {
tipQueue.pop_front();
// Signal InkHUD to open the menu on Region page
inkhud->forceRegionMenu = true;
// Close tips and open menu
sendToBackground();
inkhud->openMenu();
return;
}
// Consume current tip
tipQueue.pop_front();
// All tips done
if (tipQueue.empty()) {
// Record that user has now seen the "tutorial" set of tips
// Don't show them on subsequent boots
if (settings->tips.firstBoot && !needsRegion) {
if (settings->tips.firstBoot) {
settings->tips.firstBoot = false;
inkhud->persistence->saveSettings();
}
// Close applet and clean the screen
// Close applet, and full refresh to clean the screen
// Need to force update, because our request would be ignored otherwise, as we are now background
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} else {
requestUpdate();
}
// More tips left
else
requestUpdate();
}
// Functions the same as the user button in this instance

View File

@@ -23,7 +23,6 @@ class TipsApplet : public SystemApplet
enum class Tip {
WELCOME,
FINISH_SETUP,
PICK_REGION,
SAFE_SHUTDOWN,
CUSTOMIZATION,
BUTTONS,

View File

@@ -276,15 +276,6 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
return 0; // We agree: deep sleep now
}
// Display an intermediate screen while configuration changes are applied
void InkHUD::Events::applyingChanges()
{
// Bring the logo applet forward with a temporary message
for (SystemApplet *sa : inkhud->systemApplets) {
sa->onApplyingChanges();
}
}
// Callback for rebootObserver
// Same as shutdown, without drawing the logoApplet
// Makes sure we don't lose message history / InkHUD config

View File

@@ -29,13 +29,12 @@ class Events
void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press
void applyingChanges();
void onExitShort(); // Exit button: short press
void onExitLong(); // Exit button: long press
void onNavUp(); // Navigate up
void onNavDown(); // Navigate down
void onNavLeft(); // Navigate left
void onNavRight(); // Navigate right
void onExitShort(); // Exit button: short press
void onExitLong(); // Exit button: long press
void onNavUp(); // Navigate up
void onNavDown(); // Navigate down
void onNavLeft(); // Navigate left
void onNavRight(); // Navigate right
int beforeDeepSleep(void *unused); // Prepare for shutdown
int beforeReboot(void *unused); // Prepare for reboot

View File

@@ -53,13 +53,6 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive,
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
}
void InkHUD::InkHUD::notifyApplyingChanges()
{
if (events) {
events->applyingChanges();
}
}
// Start InkHUD!
// Call this only after you have configured InkHUD
void InkHUD::InkHUD::begin()

View File

@@ -47,7 +47,6 @@ class InkHUD
void setDriver(Drivers::EInk *driver);
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
void notifyApplyingChanges();
void begin();
@@ -77,9 +76,6 @@ class InkHUD
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
void toggleBatteryIcon();
// Used by TipsApplet to force menu to start on Region selection
bool forceRegionMenu = false;
// Updating the display
// - called by various InkHUD components

View File

@@ -27,7 +27,6 @@ class SystemApplet : public Applet
bool lockRequests = false; // - prevent other applets from triggering display updates
virtual void onReboot() { onShutdown(); } // - handle reboot specially
virtual void onApplyingChanges() {}
// Other system applets may take precedence over our own system applet though
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)