mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-27 04:02:05 +00:00
InkHUD Extended ASCII (#6768)
* Custom AdafruitGFX fonts with extended ASCII encodings * AppletFont handles re-encoding of UTF-8 text * Manual parsing of text which may contain non-ASCII chars * Display emoji reactions, even when unprintable Important to indicate to users that a message has been received, even if meaning is unclear. * Superstitious shrink_to_fit I don't think these help, but they're not hurting! * Use Windows-1252 fonts by default * Spelling * Tidy up nicheGraphics.h * Documentation * Fix inverted logic Slipped in during a last minute renaming while tidying up to push..
This commit is contained in:
@@ -286,6 +286,10 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
bool isOurNode = node->num == nodeDB->getNodeNum();
|
||||
bool unknownHops = !node->has_hops_away && !isOurNode;
|
||||
|
||||
// Parse any non-ascii chars in the short name,
|
||||
// and use last 4 instead if unknown / can't render
|
||||
std::string shortName = parseShortName(node);
|
||||
|
||||
// We will draw a left or right hand variant, to place text towards screen center
|
||||
// Hopefully avoid text spilling off screen
|
||||
// Most values are the same, regardless of left-right handedness
|
||||
@@ -299,7 +303,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
markerSize = map(node->hops_away, 0, config.lora.hop_limit, markerSizeMax, markerSizeMin);
|
||||
|
||||
// Common dimensions (left or right variant)
|
||||
textW = getTextWidth(node->user.short_name);
|
||||
textW = getTextWidth(shortName);
|
||||
if (textW == 0)
|
||||
paddingInnerW = 0; // If no text, no padding for text
|
||||
textH = fontSmall.lineHeight();
|
||||
@@ -325,7 +329,7 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
drawRect(labelX, labelY, labelW, labelH, BLACK);
|
||||
|
||||
// Short name
|
||||
printAt(textX, textY, node->user.short_name, LEFT, MIDDLE);
|
||||
printAt(textX, textY, shortName, LEFT, MIDDLE);
|
||||
|
||||
// If the label is for our own node,
|
||||
// fade it by overdrawing partially with white
|
||||
|
||||
@@ -142,16 +142,18 @@ void InkHUD::NodeListApplet::onRender()
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
||||
|
||||
// -- Shortname --
|
||||
// use "?" if unknown
|
||||
if (node && node->has_user)
|
||||
shortName = node->user.short_name;
|
||||
// Parse special chars in the short name
|
||||
// Use "?" if unknown
|
||||
if (node)
|
||||
shortName = parseShortName(node);
|
||||
else
|
||||
shortName = "?";
|
||||
|
||||
// -- Longname --
|
||||
// use node id if unknown
|
||||
// Parse special chars in long name
|
||||
// Use node id if unknown
|
||||
if (node && node->has_user)
|
||||
longName = node->user.long_name; // Found in nodeDB
|
||||
longName = parse(node->user.long_name); // Found in nodeDB
|
||||
else {
|
||||
// Not found in nodeDB, show a hex nodeid instead
|
||||
longName = hexifyNodeNum(nodeNum);
|
||||
|
||||
@@ -9,6 +9,12 @@ using namespace NicheGraphics;
|
||||
void InkHUD::BasicExampleApplet::onRender()
|
||||
{
|
||||
printAt(0, 0, "Hello, World!");
|
||||
|
||||
// If text might contain "special characters", is needs parsing first
|
||||
// This applies to data such as text-messages and and node names
|
||||
|
||||
// std::string greeting = parse("Grüezi mitenand!");
|
||||
// printAt(0, 0, greeting);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,7 +5,7 @@
|
||||
An example of an InkHUD applet.
|
||||
Tells us when a new text message arrives.
|
||||
|
||||
This applet makes use of the MeshModule API to detect new messages,
|
||||
This applet makes use of the Module API to detect new messages,
|
||||
which is a general part of the Meshtastic firmware, and not part of InkHUD.
|
||||
|
||||
In variants/<your device>/nicheGraphics.h:
|
||||
|
||||
@@ -244,6 +244,7 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
||||
void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
{
|
||||
items.clear();
|
||||
items.shrink_to_fit();
|
||||
|
||||
switch (page) {
|
||||
case ROOT:
|
||||
|
||||
@@ -33,11 +33,6 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
|
||||
if (getFrom(p) == nodeDB->getNodeNum())
|
||||
return 0;
|
||||
|
||||
// Abort if message was only an "emoji reaction"
|
||||
// Possibly some implementation of this in future?
|
||||
if (p->decoded.emoji)
|
||||
return 0;
|
||||
|
||||
Notification n;
|
||||
n.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
||||
|
||||
@@ -122,7 +117,7 @@ void InkHUD::NotificationApplet::onRender()
|
||||
int16_t textM = divX + padW + (getTextWidth(text) / 2);
|
||||
|
||||
// Restrict area for printing
|
||||
// - don't overlap border, or diveder
|
||||
// - don't overlap border, or divider
|
||||
setCrop(divX + 1, 1, (width() - (divX + 1) - 1), height() - 2);
|
||||
|
||||
// Drop shadow
|
||||
@@ -241,7 +236,8 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
// Parse any non-ascii characters and return
|
||||
return parse(text);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -23,9 +23,9 @@ void InkHUD::PairingApplet::onRender()
|
||||
|
||||
// Device's bluetooth name, if it will fit
|
||||
setFont(fontSmall);
|
||||
std::string name = "Name: " + std::string(getDeviceName());
|
||||
std::string name = "Name: " + parse(getDeviceName());
|
||||
if (getTextWidth(name) > width()) // Too wide, try without the leading "Name: "
|
||||
name = std::string(getDeviceName());
|
||||
name = parse(getDeviceName());
|
||||
if (getTextWidth(name) < width()) // Does it fit?
|
||||
printAt(X(0.5), Y(0.75), name, CENTER, MIDDLE);
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
|
||||
if (getFrom(p) == nodeDB->getNodeNum())
|
||||
return 0;
|
||||
|
||||
// Abort if message was only an "emoji reaction"
|
||||
// Possibly some implemetation of this in future?
|
||||
if (p->decoded.emoji)
|
||||
return 0;
|
||||
|
||||
requestAutoshow(); // Want to become foreground, if permitted
|
||||
requestUpdate(); // Want to update display, if applet is foreground
|
||||
|
||||
@@ -100,19 +95,22 @@ void InkHUD::AllMessageApplet::onRender()
|
||||
// Print message text
|
||||
// ===================
|
||||
|
||||
// Parse any non-ascii chars in the message
|
||||
std::string text = parse(message->text);
|
||||
|
||||
// Extra gap below the header
|
||||
int16_t textTop = headerDivY + padDivH;
|
||||
|
||||
// Determine size if printed large
|
||||
setFont(fontLarge);
|
||||
uint32_t textHeight = getWrappedTextHeight(0, width(), message->text);
|
||||
uint32_t textHeight = getWrappedTextHeight(0, width(), 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(), message->text);
|
||||
printWrapped(0, textTop, width(), text);
|
||||
}
|
||||
|
||||
// Don't show notifications for text messages when our applet is displayed
|
||||
|
||||
@@ -23,11 +23,6 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
|
||||
if (!isActive())
|
||||
return 0;
|
||||
|
||||
// Abort if only an "emoji reactions"
|
||||
// Possibly some implemetation of this in future?
|
||||
if (p->decoded.emoji)
|
||||
return 0;
|
||||
|
||||
// If DM (not broadcast)
|
||||
if (!isBroadcast(p->to)) {
|
||||
// Want to update display, if applet is foreground
|
||||
@@ -96,19 +91,22 @@ void InkHUD::DMApplet::onRender()
|
||||
// Print message text
|
||||
// ===================
|
||||
|
||||
// Parse any non-ascii chars in the message
|
||||
std::string text = parse(latestMessage->dm.text);
|
||||
|
||||
// Extra gap below the header
|
||||
int16_t textTop = headerDivY + padDivH;
|
||||
|
||||
// Determine size if printed large
|
||||
setFont(fontLarge);
|
||||
uint32_t textHeight = getWrappedTextHeight(0, width(), latestMessage->dm.text);
|
||||
uint32_t textHeight = getWrappedTextHeight(0, width(), 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(), text);
|
||||
}
|
||||
|
||||
// Don't show notifications for direct messages when our applet is displayed
|
||||
|
||||
@@ -16,7 +16,7 @@ void InkHUD::HeardApplet::onActivate()
|
||||
|
||||
void InkHUD::HeardApplet::onDeactivate()
|
||||
{
|
||||
// Avoid an unlikely situation where frquent activation / deactivation populated duplicate info from node DB
|
||||
// Avoid an unlikely situation where frequent activation / deactivation populates duplicate info from node DB
|
||||
cards.clear();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ void InkHUD::HeardApplet::handleParsed(CardInfo c)
|
||||
|
||||
cards.push_front(c); // Insert into base class' card collection
|
||||
cards.resize(min(maxCards(), (uint8_t)cards.size())); // Don't keep more cards than we could *ever* fit on screen
|
||||
cards.shrink_to_fit();
|
||||
|
||||
// Our rendered image needs to change if:
|
||||
if (previous.nodeNum != c.nodeNum // Different node
|
||||
@@ -54,7 +55,7 @@ void InkHUD::HeardApplet::handleParsed(CardInfo c)
|
||||
}
|
||||
|
||||
// When applet is activated, pre-fill with stale data from NodeDB
|
||||
// We're sorting using the last_heard value. Succeptible to weirdness if node's RTC changes.
|
||||
// We're sorting using the last_heard value. Susceptible to weirdness if node's RTC changes.
|
||||
// No SNR is available in node db, so we can't calculate signal either
|
||||
// These initial cards from node db will be gradually pushed out by new packets which originate from out base applet instead
|
||||
void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
@@ -72,7 +73,7 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
return (top->last_heard > bottom->last_heard);
|
||||
});
|
||||
|
||||
// Keep the most recent entries onlyt
|
||||
// Keep the most recent entries only
|
||||
// Just enough to fill the screen
|
||||
if (ordered.size() > maxCards())
|
||||
ordered.resize(maxCards());
|
||||
|
||||
@@ -53,6 +53,7 @@ void InkHUD::RecentsListApplet::handleParsed(CardInfo c)
|
||||
|
||||
cards.push_front(c); // Store this CardInfo
|
||||
cards.resize(min(maxCards(), (uint8_t)cards.size())); // Don't keep more cards than we could *ever* fit on screen
|
||||
cards.shrink_to_fit();
|
||||
|
||||
// Record the time of this observation
|
||||
// Used to count active nodes, and to know when to prune inactive nodes
|
||||
@@ -99,10 +100,12 @@ void InkHUD::RecentsListApplet::prune()
|
||||
if (!isActive(ages.at(i).seenAtMs)) {
|
||||
// Drop this item, and all others behind it
|
||||
ages.resize(i);
|
||||
ages.shrink_to_fit();
|
||||
cards.resize(i);
|
||||
cards.shrink_to_fit();
|
||||
|
||||
// Request an update, if pruning did modify our data
|
||||
// Required if pruning was scheduled. Redundent if pruning was prior to rendering.
|
||||
// Required if pruning was scheduled. Redundant if pruning was prior to rendering.
|
||||
requestAutoshow();
|
||||
requestUpdate();
|
||||
|
||||
|
||||
@@ -71,27 +71,28 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
||||
MessageStore::Message &m = store->messages.at(i);
|
||||
bool outgoing = (m.sender == 0);
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
|
||||
std::string bodyText = parse(m.text); // Parse any non-ascii chars in the message
|
||||
|
||||
// Cache bottom Y of message text
|
||||
// - Used when drawing vertical line alongside
|
||||
const int16_t dotsB = msgB;
|
||||
|
||||
// Get dimensions for message text
|
||||
uint16_t bodyH = getWrappedTextHeight(msgL, msgW, m.text);
|
||||
uint16_t bodyH = getWrappedTextHeight(msgL, msgW, bodyText);
|
||||
int16_t bodyT = msgB - bodyH;
|
||||
|
||||
// Print message
|
||||
// - if incoming
|
||||
if (!outgoing)
|
||||
printWrapped(msgL, bodyT, msgW, m.text);
|
||||
printWrapped(msgL, bodyT, msgW, bodyText);
|
||||
|
||||
// Print message
|
||||
// - if outgoing
|
||||
else {
|
||||
if (getTextWidth(m.text) < width()) // If short,
|
||||
printAt(msgR, bodyT, m.text, RIGHT); // print right align
|
||||
else // If long,
|
||||
printWrapped(msgL, bodyT, msgW, m.text); // need printWrapped(), which doesn't support right align
|
||||
if (getTextWidth(bodyText) < width()) // If short,
|
||||
printAt(msgR, bodyT, bodyText, RIGHT); // print right align
|
||||
else // If long,
|
||||
printWrapped(msgL, bodyT, msgW, bodyText); // need printWrapped(), which doesn't support right align
|
||||
}
|
||||
|
||||
// Move cursor up
|
||||
@@ -103,12 +104,16 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
||||
// - shortname, if possible, or "me"
|
||||
// - time received, if possible
|
||||
std::string info;
|
||||
if (sender && sender->has_user)
|
||||
info += sender->user.short_name;
|
||||
else if (outgoing)
|
||||
if (outgoing)
|
||||
info += "Me";
|
||||
else
|
||||
info += hexifyNodeNum(m.sender);
|
||||
else {
|
||||
// Check if sender is node db
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
|
||||
if (sender)
|
||||
info += parseShortName(sender); // Handle any unprintable chars in short name
|
||||
else
|
||||
info += hexifyNodeNum(m.sender); // No node info at all. Print the node num
|
||||
}
|
||||
|
||||
std::string timeString = getTimeString(m.timestamp);
|
||||
if (timeString.length() > 0) {
|
||||
@@ -195,11 +200,6 @@ int InkHUD::ThreadedMessageApplet::onReceiveTextMessage(const meshtastic_MeshPac
|
||||
if (p->to != NODENUM_BROADCAST)
|
||||
return 0;
|
||||
|
||||
// Abort if messages was an "emoji reaction"
|
||||
// Possibly some implemetation of this in future?
|
||||
if (p->decoded.emoji)
|
||||
return 0;
|
||||
|
||||
// Extract info into our slimmed-down "StoredMessage" type
|
||||
MessageStore::Message newMessage;
|
||||
newMessage.timestamp = getValidTime(RTCQuality::RTCQualityDevice, true); // Current RTC time
|
||||
|
||||
Reference in New Issue
Block a user