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

@@ -6,8 +6,6 @@ using namespace NicheGraphics;
void InkHUD::MapApplet::onRender()
{
setFont(fontSmall);
// Abort if no markers to render
if (!enoughMarkers()) {
printAt(X(0.5), Y(0.5) - (getFont().lineHeight() / 2), "Node positions", CENTER, MIDDLE);
@@ -27,6 +25,7 @@ void InkHUD::MapApplet::onRender()
// Set the region shown on the map
// - default: fit all nodes, plus padding
// - maybe overriden by derived applet
// - getMapSize *sets* passed parameters (C-style)
getMapSize(&widthMeters, &heightMeters);
// Set the metersToPx conversion value
@@ -71,7 +70,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
// - uses tan to find angles for lat / long degrees
// - longitude: triangle formed by x and y (on plane of the equator)
// - latitude: triangle formed by z (north south),
// and the line along plane of equator which stetches from earth's axis to where point xyz intersects planet's surface
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
// Working totals, averaged after nodeDB processed
uint32_t positionCount = 0;
@@ -134,7 +133,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
// Latitude from cartesian cooods
// Latitude from cartesian coords
// (Angle from 3D coords describing a point on the globe's surface)
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
@@ -191,8 +190,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
// Longitude is trickier
float lng = node->position.longitude_i * 1e-7;
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees travelled east from lngCenter to reach node
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees travelled west from lngCenter to reach node
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
if (degEastward < degWestward)
easternmost = max(easternmost, lngCenter + degEastward);
else
@@ -258,7 +257,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
// Find x and y position based on node's position in nodeDB
assert(nodeDB->hasValidPosition(node));
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
node->has_hops_away, // Is the hopsAway number valid
node->hops_away // Hops away
);
@@ -288,7 +287,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
bool unknownHops = !node->has_hops_away && !isOurNode;
// We will draw a left or right hand variant, to place text towards screen center
// Hopfully avoid text spilling off screen
// Hopefully avoid text spilling off screen
// Most values are the same, regardless of left-right handedness
// Pick emblem style
@@ -388,7 +387,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
// Calculate marker and store it
markers.push_back(
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, convered from Meshtastic's internal int32 style
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
node->has_hops_away, // Is the hopsAway number valid
node->hops_away // Hops away
));

View File

@@ -38,13 +38,12 @@ class MapApplet : public Applet
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
private:
// Position of markers to be drawn, relative to map center
// HopsAway info used to determine marker size
// Position and size of a marker to be drawn
struct Marker {
float eastMeters = 0; // Meters east of mapCenter. Negative if west.
float northMeters = 0; // Meters north of mapCenter. Negative if south.
float eastMeters = 0; // Meters east of map center. Negative if west.
float northMeters = 0; // Meters north of map center. Negative if south.
bool hasHopsAway = false;
uint8_t hopsAway = 0;
uint8_t hopsAway = 0; // Determines marker size
};
Marker calculateMarker(float lat, float lng, bool hasHopsAway, uint8_t hopsAway);

View File

@@ -12,7 +12,7 @@ using namespace NicheGraphics;
InkHUD::NodeListApplet::NodeListApplet(const char *name) : MeshModule(name)
{
// We only need to be promiscuous in order to hear NodeInfo, apparently. See NodeInfoModule
// For all other packets, we manually reimplement isPromiscuous=false in wantPacket
// For all other packets, we manually act as if isPromiscuous=false, in wantPacket
MeshModule::isPromiscuous = true;
}
@@ -25,17 +25,17 @@ bool InkHUD::NodeListApplet::wantPacket(const meshtastic_MeshPacket *p)
&& (isToUs(p) || isBroadcast(p->to) || // Either: intended for us,
p->decoded.portnum == meshtastic_PortNum_NODEINFO_APP); // or nodeinfo
// Note: special handling of NodeInfo is to match NodeInfoModule
// To match the behavior seen in the client apps:
// - NodeInfoModule's ProtoBufModule base is "promiscuous"
// - All other activity is *not* promiscuous
// To achieve this, our MeshModule *is* promiscious, and we're manually reimplementing non-promiscuous behavior here,
// To achieve this, our MeshModule *is* promiscuous, and we're manually reimplementing non-promiscuous behavior here,
// to match the code in MeshModule::callModules
}
// MeshModule packets arrive here
// Extract the info and pass it to the derived applet
// Derived applet will store the CardInfo and perform any required sorting of the CardInfo collection
// Derived applet will store the CardInfo, and perform any required sorting of the CardInfo collection
// Derived applet might also need to keep other tallies (active nodes count?)
ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacket &mp)
{
@@ -76,8 +76,8 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
}
// Maximum number of cards we may ever need to render, in our tallest layout config
// May be slightly in excess of the true value: header not accounted for
// Calculate maximum number of cards we may ever need to render, in our tallest layout config
// Number might be slightly in excess of the true value: applet header text not accounted for
uint8_t InkHUD::NodeListApplet::maxCards()
{
// Cache result. Shouldn't change during execution
@@ -87,7 +87,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
const uint16_t height = Tile::maxDisplayDimension();
// Use a loop instead of arithmetic, because it's easier for my brain to follow
// Add cards one by one, until the latest card (without margin) extends below screen
// Add cards one by one, until the latest card extends below screen
uint16_t y = cardH; // First card: no margin above
cards = 1;
@@ -102,7 +102,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
return cards;
}
// Draw using info which derived applet placed into NodeListApplet::cards for us
// Draw, using info which derived applet placed into NodeListApplet::cards for us
void InkHUD::NodeListApplet::onRender()
{
@@ -120,9 +120,6 @@ void InkHUD::NodeListApplet::onRender()
// Draw the main node list
// ========================
// const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
// const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH;
// Imaginary vertical line dividing left-side and right-side info
// Long-name will crop here
const uint16_t dividerX = (width() - 1) - getTextWidth("X Hops");
@@ -215,9 +212,8 @@ void InkHUD::NodeListApplet::onRender()
// Once we've run out of screen, stop drawing cards
// Depending on tiles / rotation, this may be before we hit maxCards
if (cardTopY > height()) {
if (cardTopY > height())
break;
}
}
}
@@ -246,20 +242,20 @@ void InkHUD::NodeListApplet::drawSignalIndicator(int16_t x, int16_t y, uint16_t
constexpr float paddingW = 0.1; // Either side
constexpr float paddingH = 0.1; // Above and below
constexpr float gutterX = 0.1; // Between bars
constexpr float gutterW = 0.1; // Between bars
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the talleest
constexpr float barHRel[] = {0.3, 0.5, 0.7, 1.0}; // Heights of the signal bars, relative to the tallest
constexpr uint8_t barCount = 4; // How many bars we draw. Reference only: changing value won't change the count.
// Dynamically calculate the width of the bars, and height of the rightmost, relative to other dimensions
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterX) + paddingW)) / barCount;
float barW = (1.0 - (paddingW + ((barCount - 1) * gutterW) + paddingW)) / barCount;
float barHMax = 1.0 - (paddingH + paddingH);
// Draw signal bar rectangles, then placeholder lines once strength reached
for (uint8_t i = 0; i < barCount; i++) {
// Co-ords for this specific bar
// Coords for this specific bar
float barH = barHMax * barHRel[i];
float barX = paddingW + (i * (gutterX + barW));
float barX = paddingW + (i * (gutterW + barW));
float barY = paddingH + (barHMax - barH);
// Rasterize to px coords at the last moment

View File

@@ -23,13 +23,16 @@ Used by the "Recents" and "Heard" applets. Possibly more in future?
#include "graphics/niche/InkHUD/Applet.h"
#include "main.h"
namespace NicheGraphics::InkHUD
{
class NodeListApplet : public Applet, public MeshModule
{
protected:
// Info used to draw one card to the node list
// Info needed to draw a node card to the list
// - generated each time we hear a node
struct CardInfo {
static constexpr uint8_t HOPS_UNKNOWN = -1;
static constexpr uint32_t DISTANCE_UNKNOWN = -1;
@@ -37,31 +40,31 @@ class NodeListApplet : public Applet, public MeshModule
NodeNum nodeNum = 0;
SignalStrength signal = SignalStrength::SIGNAL_UNKNOWN;
uint32_t distanceMeters = DISTANCE_UNKNOWN;
uint8_t hopsAway = HOPS_UNKNOWN; // Unknown
uint8_t hopsAway = HOPS_UNKNOWN;
};
public:
NodeListApplet(const char *name);
void onRender() override;
// MeshModule overrides
virtual bool wantPacket(const meshtastic_MeshPacket *p) override;
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
bool wantPacket(const meshtastic_MeshPacket *p) override;
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
protected:
virtual void handleParsed(CardInfo c) = 0; // Pass extracted info from a new packet to derived class, for sorting and storage
virtual std::string getHeaderText() = 0; // Title for the applet's header. Todo: get this info another way?
virtual void handleParsed(CardInfo c) = 0; // Tell derived applet that we heard a node
virtual std::string getHeaderText() = 0; // Ask derived class what the applet's title should be
uint8_t maxCards(); // Calculate the maximum number of cards an applet could ever display
uint8_t maxCards(); // Max number of cards which could ever fit on screen
std::deque<CardInfo> cards; // Derived applet places cards here, for this base applet to render
std::deque<CardInfo> cards; // Cards to be rendered. Derived applet fills this.
private:
// UI element: a "mobile phone" style signal indicator
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h, SignalStrength signal);
void drawSignalIndicator(int16_t x, int16_t y, uint16_t w, uint16_t h,
SignalStrength signal); // Draw a "mobile phone" style signal indicator
// Dimensions for drawing
// Used for render, and also for maxCards calc
// Card Dimensions
// - for rendering and for maxCards calc
const uint8_t cardMarginH = fontSmall.lineHeight() / 2; // Gap between cards
const uint16_t cardH = fontLarge.lineHeight() + fontSmall.lineHeight() + cardMarginH; // Height of card
};

View File

@@ -36,8 +36,6 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
// We should always be ready to draw
void InkHUD::NewMsgExampleApplet::onRender()
{
setFont(fontSmall);
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
int16_t centerX = X(0.5); // Same as width() / 2

View File

@@ -53,7 +53,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
// Store info from handleReceived
bool haveMessage = false;
NodeNum fromWho;
NodeNum fromWho = 0;
};
} // namespace NicheGraphics::InkHUD

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

View File

@@ -41,14 +41,12 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
void InkHUD::AllMessageApplet::onRender()
{
setFont(fontSmall);
// Find newest message, regardless of whether DM or broadcast
MessageStore::Message *message;
if (latestMessage.wasBroadcast)
message = &latestMessage.broadcast;
if (latestMessage->wasBroadcast)
message = &latestMessage->broadcast;
else
message = &latestMessage.dm;
message = &latestMessage->dm;
// Short circuit: no text message
if (!message->sender) {

View File

@@ -44,10 +44,8 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
void InkHUD::DMApplet::onRender()
{
setFont(fontSmall);
// Abort if no text message
if (!latestMessage.dm.sender) {
if (!latestMessage->dm.sender) {
printAt(X(0.5), Y(0.5), "No DMs", CENTER, MIDDLE);
return;
}
@@ -63,7 +61,7 @@ void InkHUD::DMApplet::onRender()
// RX Time
// - if valid
std::string timeString = getTimeString(latestMessage.dm.timestamp);
std::string timeString = getTimeString(latestMessage->dm.timestamp);
if (timeString.length() > 0) {
header += timeString;
header += ": ";
@@ -72,14 +70,14 @@ void InkHUD::DMApplet::onRender()
// Sender's id
// - shortname, if available, or
// - node id
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage.dm.sender);
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
if (sender && sender->has_user) {
header += sender->user.short_name;
header += " (";
header += sender->user.long_name;
header += ")";
} else
header += hexifyNodeNum(latestMessage.dm.sender);
header += hexifyNodeNum(latestMessage->dm.sender);
// Draw a "standard" applet header
drawHeader(header);
@@ -103,14 +101,14 @@ void InkHUD::DMApplet::onRender()
// Determine size if printed large
setFont(fontLarge);
uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage.dm.text);
uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage->dm.text);
// If too large, swap to small font
if (textHeight + textTop > (uint32_t)height()) // (compare signed and unsigned)
setFont(fontSmall);
// Print text
printWrapped(0, textTop, width(), latestMessage.dm.text);
printWrapped(0, textTop, width(), latestMessage->dm.text);
}
// Don't show notifications for direct messages when our applet is displayed

View File

@@ -29,13 +29,13 @@ class PositionsApplet : public MapApplet, public SinglePortModule
protected:
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
NodeNum lastFrom; // Sender of most recent (non-local) position packet
float lastLat;
float lastLng;
float lastHopsAway;
NodeNum lastFrom = 0; // Sender of most recent (non-local) position packet
float lastLat = 0.0;
float lastLng = 0.0;
float lastHopsAway = 0;
float ourLastLat; // Info about the most recent (non-local) position packet
float ourLastLng; // Info about most recent *local* position
float ourLastLat = 0.0; // Info about the most recent (non-local) position packet
float ourLastLng = 0.0; // Info about most recent *local* position
};
} // namespace NicheGraphics::InkHUD

View File

@@ -122,7 +122,7 @@ bool InkHUD::RecentsListApplet::isActive(uint32_t seenAtMs)
uint32_t now = millis();
uint32_t secsAgo = (now - seenAtMs) / 1000UL; // millis() overflow safe
return (secsAgo < settings.recentlyActiveSeconds);
return (secsAgo < settings->recentlyActiveSeconds);
}
// Text to be shown at top of applet
@@ -134,7 +134,7 @@ std::string InkHUD::RecentsListApplet::getHeaderText()
// Print the length of our "Recents" time-window
text += "Last ";
text += to_string(settings.recentlyActiveSeconds / 60);
text += to_string(settings->recentlyActiveSeconds / 60);
text += " mins";
// Print the node count

View File

@@ -23,8 +23,6 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex) : cha
void InkHUD::ThreadedMessageApplet::onRender()
{
setFont(fontSmall);
// =============
// Draw a header
// =============

View File

@@ -33,7 +33,7 @@ class Applet;
class ThreadedMessageApplet : public Applet
{
public:
ThreadedMessageApplet(uint8_t channelIndex);
explicit ThreadedMessageApplet(uint8_t channelIndex);
ThreadedMessageApplet() = delete;
void onRender() override;