mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-03 16:41:56 +00:00
* Decouple ButtonThread from sleep.cpp Reorganize sleep observables. Don't call ButtonThread methods inside doLightSleep. Instead, handle in class with new lightsleep Observables. * InkHUD: initial commit (WIP) Publicly discloses the current work in progress. Not ready for use. * feat: battery icon * chore: implement meshtastic/firmware #5454 Clean up some inline functions * feat: menu & settings for "jump to applet" * Remove the beforeRender pattern It hugely complicates things. If we can achieve acceptable performance without it, so much the better. * Remove previous Map Applet Needs re-implementation to work without the beforeRender pattern * refactor: reimplement map applet Doesn't require own position Doesn't require the beforeRender pattern to precalculate; now all-at-once in render Lays groundwork for fixed-size map with custom background image * feat: autoshow Allow user to select which applets (if any) should be automatically brought to foreground when they have new data to display * refactor: tidy-up applet constructors misc. jobs including: - consistent naming - move initializer-list-only constructors to header - give derived applets unique identifiers for MeshModule and OSThread logging * hotfix: autoshow always uses FAST update In future, it *will* often use FAST, but this will be controlled by a WindowManager component which has not yet been written. Hotfixed, in case anybody is attempting to use this development version on their deployed devices. * refactor: bringToForeground no longer requests FAST update In situations where an applet has moved to foreground because of user input, requestUpdate can be manually called, to upgrade to FAST refresh. More permanent solution for #23e1dfc * refactor: extract string storage from ThreadedMessageApplet Separates the code responsible for storing the limited message history, which was previously part of the ThreadedMessageApplet. We're now also using this code to store the "most recent message". Previously, this was stored in the `InkHUD::settings` struct, which was much less space-efficient. We're also now storing the latest DM, laying the foundation for an applet to display only DMs, which will complement the threaded message applet. * fix: text wrapping Attempts to fix a disparity between `Applet::printWrapped` and `Applet::getWrappedTextHeight`, which would occasionally cause a ThreadedMessageApplet message to render "too short", overlapping other text. * fix: purge old constructor This one slipped through the last commit.. * feat: DM Applet Useful in combination with the ThreadedMessageApplets, which don't show DMs * fix: applets shouldn't handle events while deactivated Only one or two applets were actually doing this, but I'm making a habit of having all applets return early from their event handling methods (as good practice), even if those methods are disabled elsewhere (e.g. not observing observable, return false from wantPacket) * refactor: allow requesting update without requesting autoshow Some applets may want to redraw, if they are displayed, but not feel the information is worth being brought to foreground for. Example: ActiveNodesApplet, when purging old nodes from list. * feat: custom "Recently Active" duration Allows users to tailor how long nodes will appear in the "Recents" applets, to suit the activity level of their mesh. * refactor: rename some applets * fix: autoshow * fix: getWrappedTextHeight Remove the "simulate" option from printWrapped; too hard to keep inline with genuine printing (because of AdafruitGFX Fonts' xAdvance, mabye?). Instead of simulating, we printWrapped as normal, and discard pixel output by setting crop. Both methods are similarly inefficient, apparently. * fix: text wrapping in ThreadedMessageApplet Wrong arguments were passed to Applet::printWrapped * feat: notifications for text messages Only shown if current applet does not already display the same info. Autoshow takes priority over notifications, if both would be used to display the same info. * feat: optimize FAST vs FULL updates New UpdateMediator class counts the number of each update type, and suggets which one to use, if the code doesn't already have an explicit prefence. Also performs "maintenance refreshes" unprovoked if display is not given an opportunity to before a FULL refresh through organic use. * chore: update todo list * fix: rare lock-up of buttons * refactor: backlight Replaces the initial proof-of-concept frontlight code for T-Echo Presses less than 5 seconds momentarily illuminate the display Presses longer than 5 seconds latch the light, requiring another tap to disable If user has previously removed the T-Echo's capacitive touch button (some DIY projects), the light is controlled by the on-screen menu. This fallback is used by all T-Echo devices, until a press of the capacitive touch button is detected. * feat: change tile with aux button Applied to VM-E290. Working as is, but a refactor of WindowManager::render is expected shortly, which will also tidy code from this push. * fix: specify out-of-the-box tile assignments Prevents placeholder applet showing on initial boot, for devices which use a mult-tile layout by default (VM-E290) * fix: verify settings version when loading * fix: wrong settings version * refactor: remove unimplemented argument from requestUpdate Specified whether or not to update "async", however the implementation was slightly broken, Applet::requestUpdate is only handled next time WindowManager::runOnce is called. This didn't allow code to actually await an update, which was misleading. * refactor: renaming Applet::render becomes Applet::onRender. Tile::displayedApplet becomes Tile::assignedApplet. New onRender method name allows us to move some of the pre and post render code from WindowManager into new Applet::render method, which will call onRender for us. * refactor: rendering Bit of a tidy-up. No intended change in behavior. * fix: optimize refresh times Shorter wait between retrying update if display was previously busy. Set anticipated update durations closer to observed values. No signifacant performance increase, but does decrease the amount of polling required. * feat: blocking update for E-Ink Option to wait for display update to complete before proceeding. Important when shutting down the device. * refactor: allow system applets to lock rendering Temporarily prevents other applets from rendering. * feat: boot and shutdown screens * feat: BluetoothStatus Adds a meshtastic::Status object which exposes the state of the Bluetooth connection. Intends to allow decoupling of UI code. * feat: Bluetooth pairing screen * fix: InkHUD defaults not honored * fix: random Bluetooth pin for NicheGraphics UIs * chore: button interrupts tested * fix: emoji reactions show as blank messages * fix: autoshow and notification triggered by outgoing message * feat: save InkHUD data before reboot Implemented with a new Observable. Previously, config and a few recent messages were saved on shutdown. These were lost if the device rebooted, for example when firmware settings were changed by a client. Now, the InkHUD config and recent messages saved on reboot, the same as during an intentional shutdown. * feat: imperial distances Controlled by the config.display.units setting * fix: hide features which are not yet implemented * refactor: faster rendering Previously, only tiles which requested update were re-rendered. Affected tiles had their region blanked before render, pixel by pixel. Benchmarking revealed that it is significantly faster to memset the framebuffer and redraw all tiles. * refactor: tile ownership Tiles and Applets now maintain a reciprocal link, which is enforced by asserts. Less confusing than the old situation, where an applet and a tile may disagree on their relationship. Empty tiles are now identified by a nullptr *Applet, instead of by having the placeholderApplet assigned. * fix: notifications and battery when menu open Do render notifications in front of menu; don't render battery icon in front of menu. * fix: simpler defaults Don't expose new users to multiplexed applets straight away: make them enable the feature for themselves. * fix: Inputs::TwoButton interrupts, when only one button in use * fix: ensure display update is complete when ESP32 enters light sleep Many panels power down automatically, but some require active intervention from us. If light sleep (ESP32) occurs during a display update, these panels could potentially remain powered on, applying voltage the pixels for an extended period of time, and potentially damaging the display. * fix: honor per-variant user tile limit Set as the default value for InkHUD::settings.userTiles.maxCount in nicheGraphics.h * feat: initial InkHUD support for Wireless Paper v1.1 and VM-E213 * refactor: Heard and Recents Applets Tidier code, significant speed boost. Possibly no noticable change in responsiveness, but rendering now spends much less time blocking execution, which is important for correction functioning of the other firmware components. * refactor: use a common pio base config Easier to make any future PlatformIO config changes * feat: tips Show information that we think the user might find helpful. Some info shown first boot only. Other info shown when / if relevant. * fix: text wrapping for '\n' Previously, the newline was honored, but the adojining word was not printed. * Decouple ButtonThread from sleep.cpp Reorganize sleep observables. Don't call ButtonThread methods inside doLightSleep. Instead, handle in class with new lightsleep Observables. * feat: BluetoothStatus Adds a meshtastic::Status object which exposes the state of the Bluetooth connection. Intends to allow decoupling of UI code. * feat: observable for reboot * refactor: Heltec VM-E290 installDefaultConfig * fix: random Bluetooth pin for NicheGraphics UIs
234 lines
10 KiB
C++
234 lines
10 KiB
C++
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
|
|
/*
|
|
|
|
Base class for InkHUD applets
|
|
Must be overriden
|
|
|
|
An applet is one "program" which may show info on the display.
|
|
|
|
===================================
|
|
Preliminary notes, for the curious
|
|
===================================
|
|
|
|
(This info to be streamlined, and moved to a more official documentation)
|
|
|
|
User Applets vs System Applets
|
|
-------------------------------
|
|
|
|
There are either "User Applets", or "System Applets".
|
|
This concept is only for our understanding; as far at the code is concerned, both are just "Applets"
|
|
|
|
User applets are the "normal" applets.
|
|
User applets are applets like "AllMessageApplet", or "MapApplet".
|
|
User applets may be enabled / disabled by user, via the on-screen menu.
|
|
Incorporating new UserApplets is easy: just add them during setupNicheGraphics
|
|
If a UserApplet is not added during setupNicheGraphics, it will not be built.
|
|
The set of available UserApplets is allowed to vary from device to device.
|
|
|
|
|
|
Examples of system applets include "NotificationApplet" and "MenuApplet".
|
|
For their own reasons, system applets each require some amount of special handling.
|
|
|
|
Drawing
|
|
--------
|
|
|
|
*All* drawing must be performed by an Applet.
|
|
Applets implement the onRender() method, where all drawing takes place.
|
|
Applets are told how wide and tall they are, and are expected to draw to suit this size.
|
|
When an applet draws, it uses co-ordinates in "Applet Space": between 0 and applet width/height.
|
|
|
|
Event-driven rendering
|
|
-----------------------
|
|
|
|
Applets don't render unless something on the display needs to change.
|
|
An applet is expected to determine for itself when it has new info to display.
|
|
It should interact with the firmware via the MeshModule API, via Observables, etc.
|
|
Please don't directly add hooks throughout the existing firmware code.
|
|
|
|
When an applet decides it would like to update the display, it should call requestUpdate()
|
|
The WindowManager will shortly call the onRender() method for all affected applets
|
|
|
|
An Applet may be unexpectedly asked to render at any point in time.
|
|
|
|
Applets should cache their data, but not their pixel output: they should re-render when onRender runs.
|
|
An Applet's dimensions are not know until onRender is called, so pre-rendering of UI elements is prohibited.
|
|
|
|
Tiles
|
|
-----
|
|
|
|
Applets are assigned to "Tiles".
|
|
Assigning an applet to a tile creates a reciprocal link between the two.
|
|
When an applet renders, it passes pixels to its tile.
|
|
The tile translates these to the correct position, to be placed into the fullscreen framebuffer.
|
|
User applets don't get to choose their own tile; the multiplexing is handled by the WindowManager.
|
|
System applets might do strange things though.
|
|
|
|
Foreground and Background
|
|
-------------------------
|
|
|
|
The user can cycle between applets by short-pressing the user button.
|
|
Any applets which are currently displayed on the display are "foreground".
|
|
When the user button is short pressed, and an applet is hidden, it becomes "background".
|
|
|
|
Although the WindowManager will not render background applets, they should still collect data,
|
|
so they are ready to display when they are brought to foreground again.
|
|
Even if they are in background, Applets should still request updates when an event affects them,
|
|
as the user may have given them permission to "autoshow"; bringing themselves foreground automatically
|
|
|
|
Applets can implement the onForeground and onBackground methods to handle this change in state.
|
|
They can also check their state by calling isForeground() at any time.
|
|
|
|
Active and Inactive
|
|
-------------------
|
|
|
|
The user can select which applets are available, using the onscreen applet selection menu.
|
|
Applets which are enabled in this menu are "active"; otherwise they are "inactive".
|
|
|
|
An inactive applet is expected not collect data; not to consume resources.
|
|
Applets are activated at boot, or when enabled via the menu.
|
|
They are deactivated at shutdown, or when disabled via the menu.
|
|
|
|
Applets can implement the onActivation and onDeactivation methods to handle this change in state.
|
|
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "configuration.h"
|
|
|
|
#include <GFX.h>
|
|
|
|
#include "./AppletFont.h"
|
|
#include "./Applets/System/Notification/Notification.h"
|
|
#include "./Tile.h"
|
|
#include "./Types.h"
|
|
#include "./WindowManager.h"
|
|
#include "graphics/niche/Drivers/EInk/EInk.h"
|
|
|
|
namespace NicheGraphics::InkHUD
|
|
{
|
|
|
|
using NicheGraphics::Drivers::EInk;
|
|
using std::to_string;
|
|
|
|
class Tile;
|
|
class WindowManager;
|
|
|
|
class Applet : public GFX
|
|
{
|
|
public:
|
|
Applet();
|
|
|
|
void setTile(Tile *t); // Applets draw via a tile (for multiplexing)
|
|
Tile *getTile();
|
|
|
|
void render();
|
|
bool wantsToRender(); // Check whether applet wants to render
|
|
bool wantsToAutoshow(); // Check whether applets wants to become foreground, to show new data, if permitted
|
|
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
|
|
void updateDimensions(); // Get current size from tile
|
|
void resetDrawingSpace(); // Makes sure every render starts with same parameters
|
|
|
|
// Change the applet's state
|
|
|
|
void activate();
|
|
void deactivate();
|
|
void bringToForeground();
|
|
void sendToBackground();
|
|
|
|
// Info about applet's state
|
|
|
|
bool isActive();
|
|
bool isForeground();
|
|
|
|
// Allow derived applets to handle changes in state
|
|
|
|
virtual void onRender() = 0; // All drawing happens here
|
|
virtual void onActivate() {}
|
|
virtual void onDeactivate() {}
|
|
virtual void onForeground() {}
|
|
virtual void onBackground() {}
|
|
virtual void onShutdown() {}
|
|
virtual void onButtonShortPress() {} // For use by System Applets only
|
|
virtual void onButtonLongPress() {} // For use by System Applets only
|
|
virtual void onLockAvailable() {} // For use by System Applets only
|
|
|
|
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
|
|
|
static void setDefaultFonts(AppletFont large, AppletFont small); // Set the general purpose fonts
|
|
static uint16_t getHeaderHeight(); // How tall is the "standard" applet header
|
|
|
|
const char *name = nullptr; // Shown in applet selection menu
|
|
|
|
protected:
|
|
// Place a single pixel. All drawing methods output through here
|
|
void drawPixel(int16_t x, int16_t y, uint16_t color) override;
|
|
|
|
// Tell WindowManager to update display
|
|
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED);
|
|
|
|
// Ask for applet to be moved to foreground
|
|
void requestAutoshow();
|
|
|
|
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
|
|
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
|
|
void setCrop(int16_t left, int16_t top, uint16_t width, uint16_t height); // Ignore pixels drawn outside a certain region
|
|
void resetCrop(); // Removes setCrop()
|
|
|
|
void setFont(AppletFont f);
|
|
AppletFont getFont();
|
|
|
|
uint16_t getTextWidth(std::string text);
|
|
uint16_t getTextWidth(const char *text);
|
|
|
|
void printAt(int16_t x, int16_t y, const char *text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
|
void printAt(int16_t x, int16_t y, std::string text, HorizontalAlignment ha = LEFT, VerticalAlignment va = TOP);
|
|
void printThick(int16_t xCenter, int16_t yCenter, std::string text, uint8_t thicknessX, uint8_t thicknessY);
|
|
|
|
// Print text, with per-word line wrapping
|
|
void printWrapped(int16_t left, int16_t top, uint16_t width, std::string text);
|
|
uint32_t getWrappedTextHeight(int16_t left, uint16_t width, std::string text);
|
|
|
|
void hatchRegion(int16_t x, int16_t y, uint16_t w, uint16_t h, uint8_t spacing, Color color); // Fill with sparse lines
|
|
void drawHeader(std::string text); // Draw the standard applet header
|
|
|
|
static constexpr float LOGO_ASPECT_RATIO = 1.9; // Width:Height for drawing the Meshtastic logo
|
|
uint16_t getLogoWidth(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
|
|
uint16_t getLogoHeight(uint16_t limitWidth, uint16_t limitHeight); // Size Meshtastic logo to fit within region
|
|
void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height); // Draw the meshtastic logo
|
|
|
|
std::string hexifyNodeNum(NodeNum num);
|
|
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
|
|
std::string getTimeString(uint32_t epochSeconds); // Human readable
|
|
std::string getTimeString(); // Current time, human readable
|
|
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
|
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
|
|
|
static AppletFont fontSmall, fontLarge; // General purpose fonts, used cross-applet
|
|
|
|
private:
|
|
Tile *assignedTile = nullptr; // Rendered pixels are fed into a Tile object, which translates them, then passes to WM
|
|
bool active = false; // Has the user enabled this applet (at run-time)?
|
|
bool foreground = false; // Is the applet currently drawn on a tile?
|
|
|
|
bool wantRender = false; // In some situations, checked by WindowManager when updating, to skip unneeded redrawing.
|
|
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
|
|
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
|
|
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
|
|
|
|
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
|
|
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
|
|
|
|
AppletFont currentFont; // As passed to setFont
|
|
|
|
// As set by setCrop
|
|
int16_t cropLeft;
|
|
int16_t cropTop;
|
|
uint16_t cropWidth;
|
|
uint16_t cropHeight;
|
|
};
|
|
|
|
}; // namespace NicheGraphics::InkHUD
|
|
|
|
#endif |