InkHUD refactoring (#6216)

* chore: todo.txt
* chore: comments
* fix: no fast refresh on VME290
Reverts a line of code which was accidentally committed
* refactor: god class
Divide the behavior from the old WindowManager class into several subclasses which each have a clear role.
* refactor: cppcheck medium warnings
Enough to pass github CI for now
* refactor: updateType selection
* refactor: don't use a setter for the shared AppletFonts
* fix: update prioritization
forceUpdate calls weren't being prioritized
* refactor: remove unhelpful logging
getTimeString is used for parsing our own time, but also the timestamps of messages. The "one time only" log printing will likely fire in unhelpful situations.
* fix: " "
* refactor: get rid of types.h file for enums
* Keep that sneaky todo file out of commits
This commit is contained in:
todd-herbert
2025-03-06 23:25:41 +13:00
committed by GitHub
parent b2ef92a328
commit e6a98b1d6b
70 changed files with 2381 additions and 1955 deletions

View File

@@ -4,10 +4,10 @@
using namespace NicheGraphics;
void InkHUD::BatteryIconApplet::onActivate()
InkHUD::BatteryIconApplet::BatteryIconApplet()
{
// Show at boot, if user has previously enabled the feature
if (settings.optionalFeatures.batteryIcon)
if (settings->optionalFeatures.batteryIcon)
bringToForeground();
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
@@ -15,12 +15,6 @@ void InkHUD::BatteryIconApplet::onActivate()
powerStatusObserver.observe(&powerStatus->onNewStatus);
}
void InkHUD::BatteryIconApplet::onDeactivate()
{
// Stop having onPowerStatusUpdate called
powerStatusObserver.unobserve(&powerStatus->onNewStatus);
}
// We handle power status' even when the feature is disabled,
// so that we have up to date data ready if the feature is enabled later.
// Otherwise could be 30s before new status update, with weird battery value displayed
@@ -41,7 +35,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
// If rounded value has changed, trigger a display update
// It's okay to requestUpdate before we store the new value, as the update won't run until next loop()
// Don't trigger an update if the feature is disabled
if (this->socRounded != newSocRounded && settings.optionalFeatures.batteryIcon)
if (this->socRounded != newSocRounded && settings->optionalFeatures.batteryIcon)
requestUpdate();
// Store the new value

View File

@@ -11,24 +11,22 @@ It should be optional, enabled by the on-screen menu
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "PowerStatus.h"
namespace NicheGraphics::InkHUD
{
class BatteryIconApplet : public Applet
class BatteryIconApplet : public SystemApplet
{
public:
BatteryIconApplet();
void onRender() override;
void onActivate() override;
void onDeactivate() override;
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
protected:
private:
// Get informed when new information about the battery is available (via onPowerStatusUpdate method)
CallbackObserver<BatteryIconApplet, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<BatteryIconApplet, const meshtastic::Status *>(this, &BatteryIconApplet::onPowerStatusUpdate);

View File

@@ -2,15 +2,22 @@
#include "./LogoApplet.h"
#include "mesh/NodeDB.h"
using namespace NicheGraphics;
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
{
// Don't autostart the runOnce() timer
OSThread::disable();
OSThread::setIntervalFromNow(8 * 1000UL);
OSThread::enabled = true;
// Grab the WindowManager singleton, for convenience
windowManager = WindowManager::getInstance();
textLeft = "";
textRight = "";
textTitle = xstr(APP_VERSION_SHORT);
fontTitle = fontSmall;
bringToForeground();
// This is then drawn with a FULL refresh by Renderer::begin
}
void InkHUD::LogoApplet::onRender()
@@ -48,53 +55,24 @@ void InkHUD::LogoApplet::onRender()
void InkHUD::LogoApplet::onForeground()
{
// If another applet has locked the display, ask it to exit
Applet *other = windowManager->whoLocked();
if (other != nullptr)
other->sendToBackground();
windowManager->claimFullscreen(this); // Take ownership of fullscreen tile
windowManager->lock(this); // Prevent other applets from requesting updates
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // We don't actually use this input. Just blocking other applets from using it.
}
void InkHUD::LogoApplet::onBackground()
{
OSThread::disable(); // Disable auto-dismiss timer, in case applet was dismissed early (sendToBackground from outside class)
windowManager->releaseFullscreen(); // Relinquish ownership of fullscreen tile
windowManager->unlock(this); // Allow normal user applet update requests to resume
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
}
int32_t InkHUD::LogoApplet::runOnce()
{
LOG_DEBUG("Sent to background by timer");
sendToBackground();
return OSThread::disable();
}
// Begin displaying the screen which is shown at startup
// Suggest EInk::await after calling this method
void InkHUD::LogoApplet::showBootScreen()
{
OSThread::setIntervalFromNow(8 * 1000UL);
OSThread::enabled = true;
textLeft = "";
textRight = "";
textTitle = xstr(APP_VERSION_SHORT);
fontTitle = fontSmall;
bringToForeground();
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// Begin displaying the screen which is shown at shutdown
// Needs EInk::await after calling this method, to ensure display updates before shutdown
void InkHUD::LogoApplet::showShutdownScreen()
void InkHUD::LogoApplet::onShutdown()
{
textLeft = "";
textRight = "";
@@ -102,7 +80,13 @@ void InkHUD::LogoApplet::showShutdownScreen()
fontTitle = fontLarge;
bringToForeground();
requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Already requested, just upgrading to FULL
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update
}
int32_t InkHUD::LogoApplet::runOnce()
{
sendToBackground();
return OSThread::disable();
}
#endif

View File

@@ -12,24 +12,19 @@
#include "configuration.h"
#include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class LogoApplet : public Applet, public concurrency::OSThread
class LogoApplet : public SystemApplet, public concurrency::OSThread
{
public:
LogoApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
// Note: interacting directly with an applet like this is non-standard
// Only permitted because this is a "system applet", which has special behavior and interacts directly with WindowManager
void showBootScreen();
void showShutdownScreen();
void onShutdown() override;
protected:
int32_t runOnce() override;
@@ -38,8 +33,6 @@ class LogoApplet : public Applet, public concurrency::OSThread
std::string textRight;
std::string textTitle;
AppletFont fontTitle;
WindowManager *windowManager = nullptr; // For convenience
};
} // namespace NicheGraphics::InkHUD

View File

@@ -2,9 +2,11 @@
#include "./MenuApplet.h"
#include "PowerStatus.h"
#include "RTC.h"
#include "airtime.h"
#include "power.h"
using namespace NicheGraphics;
static constexpr uint8_t MENU_TIMEOUT_SEC = 60; // How many seconds before menu auto-closes
@@ -17,23 +19,16 @@ InkHUD::MenuApplet::MenuApplet() : concurrency::OSThread("MenuApplet")
{
// No timer tasks at boot
OSThread::disable();
}
void InkHUD::MenuApplet::onActivate()
{
// Grab pointers to some singleton components which the menu interacts with
// We could do this every time we needed them, in place,
// but this just makes the code tidier
this->windowManager = WindowManager::getInstance();
// Note: don't get instance if we're not actually using the backlight,
// or else you will unintentionally instantiate it
if (settings.optionalMenuItems.backlight) {
if (settings->optionalMenuItems.backlight) {
backlight = Drivers::LatchingBacklight::getInstance();
}
}
void InkHUD::MenuApplet::onActivate() {}
void InkHUD::MenuApplet::onForeground()
{
// We do need this before we render, but we can optimize by just calculating it once now
@@ -45,21 +40,23 @@ void InkHUD::MenuApplet::onForeground()
// If device has a backlight which isn't controlled by aux button:
// backlight on always when menu opens.
// Courtesy to T-Echo users who removed the capacitive touch button
if (settings.optionalMenuItems.backlight) {
if (settings->optionalMenuItems.backlight) {
assert(backlight);
if (!backlight->isOn())
backlight->peek();
}
// Prevent user applets requested update while menu is open
windowManager->lock(this);
// Prevent user applets requesting update while menu is open
// Handle button input with this applet
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true;
// Begin the auto-close timeout
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
OSThread::enabled = true;
// Upgrade the refresh to FAST, for guaranteed responsiveness
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onBackground()
@@ -67,7 +64,7 @@ void InkHUD::MenuApplet::onBackground()
// If device has a backlight which isn't controlled by aux button:
// Item in options submenu allows keeping backlight on after menu is closed
// If this item is deselected we will turn backlight off again, now that menu is closing
if (settings.optionalMenuItems.backlight) {
if (settings->optionalMenuItems.backlight) {
assert(backlight);
if (!backlight->isLatched())
backlight->off();
@@ -77,7 +74,8 @@ void InkHUD::MenuApplet::onBackground()
OSThread::disable();
// Resume normal rendering and button behavior of user applets
windowManager->unlock(this);
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Restore the user applet whose tile we borrowed
if (borrowedTileOwner)
@@ -87,8 +85,8 @@ void InkHUD::MenuApplet::onBackground()
borrowedTileOwner = nullptr;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// We're only updating here to ugrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
windowManager->forceUpdate(EInk::UpdateTypes::FAST);
// We're only updating here to upgrade from UNSPECIFIED to FAST, to ensure responsiveness when exiting menu
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
// Open the menu
@@ -140,43 +138,35 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
case NEXT_TILE:
// Note performed manually;
// WindowManager::nextTile is raised by aux button press only, and will interact poorly with the menu
settings.userTiles.focused = (settings.userTiles.focused + 1) % settings.userTiles.count;
windowManager->changeLayout();
cursor = 0; // No menu item selected, for quick exit after tile swap
cursorShown = false;
inkhud->nextTile();
break;
case ROTATE:
settings.rotation = (settings.rotation + 1) % 4;
windowManager->changeLayout();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
inkhud->rotate();
break;
case LAYOUT:
// Todo: smarter incrementing of tile count
settings.userTiles.count++;
settings->userTiles.count++;
if (settings.userTiles.count == 3) // Skip 3 tiles: not done yet
settings.userTiles.count++;
if (settings->userTiles.count == 3) // Skip 3 tiles: not done yet
settings->userTiles.count++;
if (settings.userTiles.count > settings.userTiles.maxCount) // Loop around if tile count now too high
settings.userTiles.count = 1;
if (settings->userTiles.count > settings->userTiles.maxCount) // Loop around if tile count now too high
settings->userTiles.count = 1;
windowManager->changeLayout();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Would update regardless; just selecting FULL
inkhud->updateLayout();
break;
case TOGGLE_APPLET:
settings.userApplets.active[cursor] = !settings.userApplets.active[cursor];
windowManager->changeActivatedApplets();
settings->userApplets.active[cursor] = !settings->userApplets.active[cursor];
inkhud->updateAppletSelection();
// requestUpdate(Drivers::EInk::UpdateTypes::FULL); // Select FULL, seeing how this action doesn't auto exit
break;
case ACTIVATE_APPLETS:
// Todo: remove this action? Already handled by TOGGLE_APPLET?
windowManager->changeActivatedApplets();
inkhud->updateAppletSelection();
break;
case TOGGLE_AUTOSHOW_APPLET:
@@ -185,14 +175,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
case TOGGLE_NOTIFICATIONS:
settings.optionalFeatures.notifications = !settings.optionalFeatures.notifications;
settings->optionalFeatures.notifications = !settings->optionalFeatures.notifications;
break;
case SET_RECENTS:
// Set value of settings.recentlyActiveSeconds
// Uses menu cursor to read RECENTS_OPTIONS_MINUTES array (defined at top of this file)
assert(cursor < sizeof(RECENTS_OPTIONS_MINUTES) / sizeof(RECENTS_OPTIONS_MINUTES[0]));
settings.recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes
settings->recentlyActiveSeconds = RECENTS_OPTIONS_MINUTES[cursor] * 60; // Menu items are in minutes
break;
case SHUTDOWN:
@@ -202,7 +192,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
break;
case TOGGLE_BATTERY_ICON:
windowManager->toggleBatteryIcon();
inkhud->toggleBatteryIcon();
break;
case TOGGLE_BACKLIGHT:
@@ -233,13 +223,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
switch (page) {
case ROOT:
// Optional: next applet
if (settings.optionalMenuItems.nextTile && settings.userTiles.count > 1)
if (settings->optionalMenuItems.nextTile && settings->userTiles.count > 1)
items.push_back(MenuItem("Next Tile", MenuAction::NEXT_TILE, MenuPage::ROOT)); // Only if multiple applets shown
// items.push_back(MenuItem("Send", MenuPage::SEND)); // TODO
items.push_back(MenuItem("Options", MenuPage::OPTIONS));
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
items.push_back(MenuItem("Save & Shutdown", MenuAction::SHUTDOWN));
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
@@ -252,7 +242,7 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
case OPTIONS:
// Optional: backlight
if (settings.optionalMenuItems.backlight) {
if (settings->optionalMenuItems.backlight) {
assert(backlight);
items.push_back(MenuItem(backlight->isLatched() ? "Backlight Off" : "Keep Backlight On", // Label
MenuAction::TOGGLE_BACKLIGHT, // Action
@@ -263,13 +253,13 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
items.push_back(MenuItem("Applets", MenuPage::APPLETS));
items.push_back(MenuItem("Auto-show", MenuPage::AUTOSHOW));
items.push_back(MenuItem("Recents Duration", MenuPage::RECENTS));
if (settings.userTiles.maxCount > 1)
if (settings->userTiles.maxCount > 1)
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
&settings.optionalFeatures.notifications));
items.push_back(
MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS, &settings.optionalFeatures.batteryIcon));
&settings->optionalFeatures.notifications));
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
&settings->optionalFeatures.batteryIcon));
// TODO - GPS and Wifi switches
/*
@@ -329,9 +319,6 @@ void InkHUD::MenuApplet::onRender()
if (items.size() == 0)
LOG_ERROR("Empty Menu");
// Testing only
setFont(fontSmall);
// Dimensions for the slots where we will draw menuItems
const float padding = 0.05;
const uint16_t itemH = fontSmall.lineHeight() * 2;
@@ -397,7 +384,7 @@ void InkHUD::MenuApplet::onRender()
// Testing only: circle instead of check box
if (item.checkState) {
const uint16_t cbWH = fontSmall.lineHeight(); // Checbox: width / height
const uint16_t cbWH = fontSmall.lineHeight(); // Checkbox: width / height
const int16_t cbL = itemR - X(padding) - cbWH; // Checkbox: left
const int16_t cbT = center - (cbWH / 2); // Checkbox : top
// Checkbox ticked
@@ -463,9 +450,9 @@ void InkHUD::MenuApplet::populateAppletPage()
{
assert(items.size() == 0);
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
const char *name = windowManager->getAppletName(i);
bool *isActive = &(settings.userApplets.active[i]);
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
const char *name = inkhud->userApplets.at(i)->name;
bool *isActive = &(settings->userApplets.active[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_APPLET, MenuPage::APPLETS, isActive));
}
}
@@ -477,11 +464,11 @@ void InkHUD::MenuApplet::populateAutoshowPage()
{
assert(items.size() == 0);
for (uint8_t i = 0; i < windowManager->getAppletCount(); i++) {
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
// Only add a menu item if applet is active
if (settings.userApplets.active[i]) {
const char *name = windowManager->getAppletName(i);
bool *isActive = &(settings.userApplets.autoshow[i]);
if (settings->userApplets.active[i]) {
const char *name = inkhud->userApplets.at(i)->name;
bool *isActive = &(settings->userApplets.autoshow[i]);
items.push_back(MenuItem(name, MenuAction::TOGGLE_AUTOSHOW_APPLET, MenuPage::AUTOSHOW, isActive));
}
}
@@ -599,10 +586,10 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
// Get the height of the the panel drawn at the top of the menu
// This is inefficient, as we do actually have to render the panel to determine the height
// It solves a catch-22 situtation, where slotCount needs to know panel height, and panel height needs to know slotCount
// It solves a catch-22 situation, where slotCount needs to know panel height, and panel height needs to know slotCount
uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight()
{
// Render *waay* off screen
// Render *far* off screen
uint16_t height = 0;
drawSystemInfoPanel(INT16_MIN, INT16_MIN, 1, &height);

View File

@@ -3,8 +3,9 @@
#include "configuration.h"
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/WindowManager.h"
#include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "./MenuItem.h"
#include "./MenuPage.h"
@@ -16,7 +17,7 @@ namespace NicheGraphics::InkHUD
class Applet;
class MenuApplet : public Applet, public concurrency::OSThread
class MenuApplet : public SystemApplet, public concurrency::OSThread
{
public:
MenuApplet();
@@ -30,6 +31,8 @@ class MenuApplet : public Applet, public concurrency::OSThread
void show(Tile *t); // Open the menu, onto a user tile
protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
int32_t runOnce() override;
void execute(MenuItem item); // Perform the MenuAction associated with a MenuItem, if any
@@ -41,7 +44,7 @@ class MenuApplet : public Applet, public concurrency::OSThread
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
uint16_t *height = nullptr); // Info panel at top of root menu
MenuPage currentPage;
MenuPage currentPage = MenuPage::ROOT;
uint8_t cursor = 0; // Which menu item is currently highlighted
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
@@ -50,9 +53,6 @@ class MenuApplet : public Applet, public concurrency::OSThread
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
WindowManager *windowManager = nullptr; // Convenient access to the InkHUD::WindowManager singleton
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
};
} // namespace NicheGraphics::InkHUD

View File

@@ -3,22 +3,20 @@
#include "./NotificationApplet.h"
#include "./Notification.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "meshUtils.h"
#include "modules/TextMessageModule.h"
#include "RTC.h"
using namespace NicheGraphics;
void InkHUD::NotificationApplet::onActivate()
InkHUD::NotificationApplet::NotificationApplet()
{
textMessageObserver.observe(textMessageModule);
}
// Note: This applet probably won't ever be deactivated
void InkHUD::NotificationApplet::onDeactivate()
{
textMessageObserver.unobserve(textMessageModule);
}
// Collect meta-info about the text message, and ask for approval for the notification
// No need to save the message itself; we can use the cached InkHUD::latestMessage data during render()
int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
@@ -28,7 +26,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
// Abort if feature disabled
// This is a bit clumsy, but avoids complicated handling when the feature is enabled / disabled
if (!settings.optionalFeatures.notifications)
if (!settings->optionalFeatures.notifications)
return 0;
// Abort if this is an outgoing message
@@ -36,7 +34,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
return 0;
// Abort if message was only an "emoji reaction"
// Possibly some implemetation of this in future?
// Possibly some implementation of this in future?
if (p->decoded.emoji)
return 0;
@@ -55,13 +53,16 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
n.sender = p->from;
}
// Close an old notification, if shown
dismiss();
// Check if we should display the notification
// A foreground applet might already be displaying this info
hasNotification = true;
currentNotification = n;
if (isApproved()) {
bringToForeground();
WindowManager::getInstance()->forceUpdate();
inkhud->forceUpdate();
} else
hasNotification = false; // Clear the pending notification: it was rejected
@@ -76,8 +77,6 @@ void InkHUD::NotificationApplet::onRender()
// We do need to do this with the battery though, as it is an "overlay"
fillRect(0, 0, width(), height(), WHITE);
setFont(fontSmall);
// Padding (horizontal)
const uint16_t padW = 4;
@@ -137,6 +136,28 @@ void InkHUD::NotificationApplet::onRender()
printThick(textM, height() / 2, text, 2, 1);
}
void InkHUD::NotificationApplet::onForeground()
{
handleInput = true; // Intercept the button input for our applet, so we can dismiss the notification
}
void InkHUD::NotificationApplet::onBackground()
{
handleInput = false;
}
void InkHUD::NotificationApplet::onButtonShortPress()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onButtonLongPress()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
// Called internally when we first get a "notifiable event", and then again before render,
// in case autoshow swapped which applet was displayed
@@ -148,7 +169,13 @@ bool InkHUD::NotificationApplet::isApproved()
return false;
}
return WindowManager::getInstance()->approveNotification(currentNotification);
// Ask all visible user applets for approval
for (Applet *ua : inkhud->userApplets) {
if (ua->isForeground() && !ua->approveNotification(currentNotification))
return false;
}
return true;
}
// Mark that the notification should no-longer be rendered
@@ -180,7 +207,8 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
bool isBroadcast = currentNotification.type == Notification::Type::NOTIFICATION_MESSAGE_BROADCAST;
// Pick source of message
MessageStore::Message *message = isBroadcast ? &latestMessage.broadcast : &latestMessage.dm;
MessageStore::Message *message =
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
// Find info about the sender
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);

View File

@@ -3,7 +3,7 @@
/*
Pop-up notification bar, on screen top edge
Displays information we feel is important, but which is not shown on currently focussed applet(s)
Displays information we feel is important, but which is not shown on currently focused applet(s)
E.g.: messages, while viewing map, etc
Feature should be optional; enable disable via on-screen menu
@@ -16,17 +16,21 @@ Feature should be optional; enable disable via on-screen menu
#include "concurrency/OSThread.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class NotificationApplet : public Applet
class NotificationApplet : public SystemApplet
{
public:
NotificationApplet();
void onRender() override;
void onActivate() override;
void onDeactivate() override;
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
@@ -40,8 +44,8 @@ class NotificationApplet : public Applet
std::string getNotificationText(uint16_t widthAvailable); // Get text for notification, to suit screen width
bool hasNotification = false; // Only used for assert. Todo: remove?
Notification currentNotification; // Set when something notification-worthy happens. Used by render()
bool hasNotification = false; // Only used for assert. Todo: remove?
Notification currentNotification = Notification(); // Set when something notification-worthy happens. Used by render()
};
} // namespace NicheGraphics::InkHUD

View File

@@ -6,8 +6,7 @@ using namespace NicheGraphics;
InkHUD::PairingApplet::PairingApplet()
{
// Grab the window manager singleton, for convenience
windowManager = WindowManager::getInstance();
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
}
void InkHUD::PairingApplet::onRender()
@@ -31,34 +30,22 @@ void InkHUD::PairingApplet::onRender()
printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE);
}
void InkHUD::PairingApplet::onActivate()
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
}
void InkHUD::PairingApplet::onDeactivate()
{
bluetoothStatusObserver.unobserve(&bluetoothStatus->onNewStatus);
}
void InkHUD::PairingApplet::onForeground()
{
// If another applet has locked the display, ask it to exit
Applet *other = windowManager->whoLocked();
if (other != nullptr)
other->sendToBackground();
windowManager->claimFullscreen(this); // Take ownership of the fullscreen tile
windowManager->lock(this); // Prevent user applets from requesting update
// Prevent most other applets from requesting update, and skip their rendering entirely
// Another system applet with a higher precedence can potentially ignore this
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
}
void InkHUD::PairingApplet::onBackground()
{
windowManager->releaseFullscreen(); // Relinquish ownership of the fullscreen tile
windowManager->unlock(this); // Allow normal user applet update requests to resume
// Allow normal update behavior to resume
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
@@ -75,12 +62,6 @@ int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *sta
// Store the passkey for rendering
passkey = bluetoothStatus->getPasskey();
// Make sure no other system applets have a lock on the display
// Boot screen, menu, etc
Applet *lockOwner = windowManager->whoLocked();
if (lockOwner)
lockOwner->sendToBackground();
// Show pairing screen
bringToForeground();
}

View File

@@ -10,19 +10,19 @@
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include "main.h"
namespace NicheGraphics::InkHUD
{
class PairingApplet : public Applet
class PairingApplet : public SystemApplet
{
public:
PairingApplet();
void onRender() override;
void onActivate() override;
void onDeactivate() override;
void onForeground() override;
void onBackground() override;
@@ -34,8 +34,6 @@ class PairingApplet : public Applet
CallbackObserver<PairingApplet, const meshtastic::Status *>(this, &PairingApplet::onBluetoothStatusUpdate);
std::string passkey = ""; // Passkey. Six digits, possibly with leading zeros
WindowManager *windowManager = nullptr; // For convenience. Set in constructor.
};
} // namespace NicheGraphics::InkHUD

View File

@@ -4,14 +4,6 @@
using namespace NicheGraphics;
InkHUD::PlaceholderApplet::PlaceholderApplet()
{
// Because this applet sometimes gets processed as if it were a bonafide user applet,
// it's probably better that we do give it a human readable name, just in case it comes up later.
// For genuine user applets, this is set by WindowManager::addApplet
Applet::name = "Placeholder";
}
void InkHUD::PlaceholderApplet::onRender()
{
// This placeholder applet fills its area with sparse diagonal lines

View File

@@ -9,20 +9,19 @@ Fills the area with diagonal lines
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class PlaceholderApplet : public Applet
class PlaceholderApplet : public SystemApplet
{
public:
PlaceholderApplet();
void onRender() override;
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
// The window manager decides when and where it should be rendered
// It may be drawn to several different tiles during on WindowManager::render call
// It may be drawn to several different tiles during an Renderer::render call
};
} // namespace NicheGraphics::InkHUD

View File

@@ -2,12 +2,44 @@
#include "./TipsApplet.h"
#include "graphics/niche/InkHUD/Persistence.h"
#include "main.h"
using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet()
{
// Grab the window manager singleton, for convenience
windowManager = WindowManager::getInstance();
// Decide which tips (if any) should be shown to user after the boot screen
// Welcome screen
if (settings->tips.firstBoot)
tipQueue.push_back(Tip::WELCOME);
// 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);
// 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);
// 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();
}
void InkHUD::TipsApplet::onRender()
@@ -53,7 +85,7 @@ void InkHUD::TipsApplet::onRender()
setFont(fontSmall);
std::string shutdown;
shutdown += "Before removing power, please shutdown from InkHUD menu, or a client app. \n";
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, fontLarge.lineHeight() * 1.5, width(), shutdown);
@@ -153,51 +185,31 @@ void InkHUD::TipsApplet::renderWelcome()
printAt(X(0.5), Y(1), "Press button to continue", CENTER, BOTTOM);
}
// Grab fullscreen tile, and lock the window manager, when applet is shown
void InkHUD::TipsApplet::onForeground()
{
windowManager->lock(this);
windowManager->claimFullscreen(this);
// Prevent most other applets from requesting update, and skip their rendering entirely
// Another system applet with a higher precedence can potentially ignore this
SystemApplet::lockRendering = true;
SystemApplet::lockRequests = true;
SystemApplet::handleInput = true; // Our applet should handle button input (unless another system applet grabs it first)
}
void InkHUD::TipsApplet::onBackground()
{
windowManager->releaseFullscreen();
windowManager->unlock(this);
// Allow normal update behavior to resume
SystemApplet::lockRendering = false;
SystemApplet::lockRequests = false;
SystemApplet::handleInput = false;
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::TipsApplet::onActivate()
{
// Decide which tips (if any) should be shown to user after the boot screen
void InkHUD::TipsApplet::onActivate() {}
// Welcome screen
if (settings.tips.firstBoot)
tipQueue.push_back(Tip::WELCOME);
// 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);
// 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);
// Applet will be brought to foreground when boot screen closes, via TipsApplet::onLockAvailable
}
// While our applet has the window manager locked, we will receive the button input
// While our SystemApplet::handleInput flag is true
void InkHUD::TipsApplet::onButtonShortPress()
{
tipQueue.pop_front();
@@ -206,15 +218,15 @@ void InkHUD::TipsApplet::onButtonShortPress()
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) {
settings.tips.firstBoot = false;
saveDataToFlash();
if (settings->tips.firstBoot) {
settings->tips.firstBoot = false;
inkhud->persistence->saveSettings();
}
// 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();
windowManager->forceUpdate(EInk::UpdateTypes::FULL);
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
// More tips left
@@ -222,13 +234,4 @@ void InkHUD::TipsApplet::onButtonShortPress()
requestUpdate();
}
// If the wm lock has just become availale (rendering, input), and we've still got tips, grab it!
// This situation would arise if bluetooth pairing occurs while TipsApplet was already shown (after pairing)
// Note: this event is only raised when *other* applets unlock the window manager
void InkHUD::TipsApplet::onLockAvailable()
{
if (!tipQueue.empty())
bringToForeground();
}
#endif

View File

@@ -12,12 +12,12 @@
#include "configuration.h"
#include "graphics/niche/InkHUD/Applet.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class TipsApplet : public Applet
class TipsApplet : public SystemApplet
{
protected:
enum class Tip {
@@ -37,7 +37,6 @@ class TipsApplet : public Applet
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onLockAvailable() override; // Reopen if interrupted by bluetooth pairing
protected:
void renderWelcome(); // Very first screen of tutorial