* Added keyboard option to menu. Shows a keyboard layout but does not type. * Keyboard types into text box and wraps. * send FreeText messages from the send submenu - renamed `KEYBOARD` action to `FREE_TEXT` and moved its menu location to the send submenu - opening the FreeText applet from the menu keeps the menu open and disabled the timeout - the FreeText applet writes to inkhud->freetext - the sending a canned message checks inkhud->freetext and if it isn't empty, sends and clears the inkhud->freetext * Text scrolls along with input * handle free text message completion as an event implements `handleFreeText` and `OnFreeText()` for system applets to interface with the FreeText Applet The FreeText Applet generates an `OnFreeText` event when completing a message which is handled by the first system applet with the `handleFreeText` flag set to true. The Menu Applet now handles this event. * call `onFreeText` whenever the FreeText Applet exits allows the menu to consistently restart its auto-close timeout * Add text cursor * Change UI to remove the header and make text box longer Keyboard displays captial letters for legibility Keyboard types captial letters with long press * center FreeText keys and draw symbolic buttons Move input field and keyboard drawing to their own functions: - `drawInputField()` - `drawKeyboard()` Store the keys in a 1-dimensional array Implement a matching array, `keyWidths`, to set key widths relative to the font size * Add character limit and counter * Fix softlock when hitting character limit * Move text box as its own menu page * rework FreeTextApplet into KeyboardApplet - The Keyboard Applet renders an on-screen keyboard at the lower portion of the screen. - Calling `inkhud->openKeyboard()` sends all the user applets to the background and resizes the first system applet with `handleFreeText` set to True to fit above the on-screen keyboard - `inkhud->closeKeyboard()` reverses this layout change * Fix input box rendering and add character limit to menu free text * remove FREE_TEXT menu page and use the FREE_TEXT menu action solely * force update when changing the free text message * reorganize KeyboardApplet - add comments after each row of `key[]` and `keyWidths[]` to preserve formatting - The selected key is now set using the key index directly - rowWidths are pre-calculated in the KeyboardApplet constructor - removed `drawKeyboard()` and implemented `drawKeyLabel()` * implement `Renderer::clearTile()` to clear the region below a tile * add parameter to forceUpdate() for re-rendering the full screen setting the `all` parameter to true in `inkhud->forceUpdate()` now causes the full screen buffer to clear an re-render. This is helpful for when sending applets to the background and the UI needs a clean canvas. System Applets can now set the `alwaysRender` flag true which causes it to re-render on every screen update. This is set to true in the Battery Icon Applet. * clean up tile clearing loops * implement dirty rendering to let applets draw over their previous render - `Applet::requestUpdate()` now has an optional flag to keep the old canvas - If honored, the renderer calls `render(true)` which runs `onDirtyRender()` instead of `onRender()` for said applet - The renderer will not call a dirty render if the full screen is getting re-rendered * simplify arithmetic in clearTile for better understanding * combine Applet::onRender() and Applet::onDirtyRender() into Applet::onRender(bool full) - add new `full` parameter to onRender() in every applet. This parameter can be ignored by most applets. - `Applet::requestUpdate()` has an optional flag that requests a full render by default * implement tile and partial rendering in KeyboardApplet * add comment for drawKeyLabel() * improve clarity of byte operations in clearTile() * remove typo and commented code * fix inaccurate comments * add null check to openKeyboard() and closeKeyboard() --------- Co-authored-by: zeropt <ferr0fluidmann@gmail.com> Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
189 lines
6.0 KiB
C++
189 lines
6.0 KiB
C++
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
|
|
#include "./LogoApplet.h"
|
|
|
|
#include "mesh/NodeDB.h"
|
|
|
|
using namespace NicheGraphics;
|
|
|
|
InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
|
{
|
|
OSThread::setIntervalFromNow(8 * 1000UL);
|
|
OSThread::enabled = true;
|
|
|
|
// During onboarding, show the default short name as well as the version string
|
|
// This behavior assists manufacturers during mass production, and should not be modified without good reason
|
|
if (!settings->tips.safeShutdownSeen) {
|
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
fontTitle = fontMedium;
|
|
textLeft = xstr(APP_VERSION_SHORT);
|
|
textRight = parseShortName(ourNode);
|
|
textTitle = "Meshtastic";
|
|
} else {
|
|
fontTitle = fontSmall;
|
|
textLeft = "";
|
|
textRight = "";
|
|
textTitle = xstr(APP_VERSION_SHORT);
|
|
}
|
|
|
|
bringToForeground();
|
|
// This is then drawn with a FULL refresh by Renderer::begin
|
|
}
|
|
|
|
void InkHUD::LogoApplet::onRender(bool full)
|
|
{
|
|
// Size of the region which the logo should "scale to fit"
|
|
uint16_t logoWLimit = X(0.8);
|
|
uint16_t logoHLimit = Y(0.5);
|
|
|
|
// Get the max width and height we can manage within the region, while still maintaining aspect ratio
|
|
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
|
|
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
|
|
|
|
// Where to place the center of the logo
|
|
int16_t logoCX = X(0.5);
|
|
int16_t logoCY = Y(0.5 - 0.05);
|
|
|
|
// Invert colors if black-on-white
|
|
// Used during shutdown, to resport display health
|
|
// Todo: handle this in InkHUD::Renderer instead
|
|
if (inverted) {
|
|
fillScreen(BLACK);
|
|
setTextColor(WHITE);
|
|
}
|
|
|
|
#ifdef USERPREFS_OEM_IMAGE_DATA // Custom boot screen, if defined in userPrefs.jsonc
|
|
|
|
// Only show the custom screen at startup
|
|
// This allows us to draw the usual Meshtastic logo at shutdown
|
|
// The effect is similar to the two-stage userPrefs boot screen used by BaseUI
|
|
if (millis() < 10 * 1000UL) {
|
|
|
|
// Draw the custom logo
|
|
const uint8_t logo[] = USERPREFS_OEM_IMAGE_DATA;
|
|
drawXBitmap(logoCX - (USERPREFS_OEM_IMAGE_WIDTH / 2), // Left
|
|
logoCY - (USERPREFS_OEM_IMAGE_HEIGHT / 2), // Top
|
|
logo, // XBM data
|
|
USERPREFS_OEM_IMAGE_WIDTH, // Width
|
|
USERPREFS_OEM_IMAGE_HEIGHT, // Height
|
|
inverted ? WHITE : BLACK // Color
|
|
);
|
|
|
|
// Select the largest font which will still comfortably fit the custom text
|
|
setFont(fontLarge);
|
|
if (getTextWidth(USERPREFS_OEM_TEXT) > 0.8 * width())
|
|
setFont(fontMedium);
|
|
if (getTextWidth(USERPREFS_OEM_TEXT) > 0.8 * width())
|
|
setFont(fontSmall);
|
|
|
|
// Draw custom text below logo
|
|
int16_t logoB = logoCY + (USERPREFS_OEM_IMAGE_HEIGHT / 2); // Bottom of the logo
|
|
printAt(X(0.5), logoB + Y(0.1), USERPREFS_OEM_TEXT, CENTER, TOP);
|
|
|
|
// Don't draw the normal boot screen, we've already drawn our custom version
|
|
return;
|
|
}
|
|
|
|
#endif
|
|
|
|
drawLogo(logoCX, logoCY, logoW, logoH, inverted ? WHITE : BLACK);
|
|
|
|
if (!textLeft.empty()) {
|
|
setFont(fontSmall);
|
|
printAt(0, 0, textLeft, LEFT, TOP);
|
|
}
|
|
|
|
if (!textRight.empty()) {
|
|
setFont(fontSmall);
|
|
printAt(X(1), 0, textRight, RIGHT, TOP);
|
|
}
|
|
|
|
if (!textTitle.empty()) {
|
|
int16_t logoB = logoCY + (logoH / 2); // Bottom of the logo
|
|
setFont(fontTitle);
|
|
printAt(X(0.5), logoB + Y(0.1), textTitle, CENTER, TOP);
|
|
}
|
|
}
|
|
|
|
void InkHUD::LogoApplet::onForeground()
|
|
{
|
|
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()
|
|
{
|
|
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, true);
|
|
}
|
|
|
|
// Begin displaying the screen which is shown at shutdown
|
|
void InkHUD::LogoApplet::onShutdown()
|
|
{
|
|
bringToForeground();
|
|
|
|
textLeft = "";
|
|
textRight = "";
|
|
textTitle = "Shutting Down...";
|
|
fontTitle = fontSmall;
|
|
|
|
// Draw a shutting down screen, twice.
|
|
// Once white on black, once black on white.
|
|
// Intention is to restore display health.
|
|
|
|
inverted = true;
|
|
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
|
delay(1000); // Cooldown. Back to back updates aren't great for health.
|
|
inverted = false;
|
|
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
|
delay(1000); // Cooldown
|
|
|
|
// Prepare for the powered-off screen now
|
|
// We can change these values because the initial "shutting down" screen has already rendered at this point
|
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
textLeft = "";
|
|
textRight = "";
|
|
textTitle = parseShortName(ourNode);
|
|
fontTitle = fontMedium;
|
|
|
|
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
|
}
|
|
|
|
void InkHUD::LogoApplet::onApplyingChanges()
|
|
{
|
|
bringToForeground();
|
|
|
|
textLeft = "";
|
|
textRight = "";
|
|
textTitle = "Applying changes";
|
|
fontTitle = fontSmall;
|
|
|
|
inkhud->forceUpdate(Drivers::EInk::FAST, false);
|
|
}
|
|
|
|
void InkHUD::LogoApplet::onReboot()
|
|
{
|
|
bringToForeground();
|
|
|
|
textLeft = "";
|
|
textRight = "";
|
|
textTitle = "Rebooting...";
|
|
fontTitle = fontSmall;
|
|
|
|
inkhud->forceUpdate(Drivers::EInk::FULL, true, false);
|
|
// Perform the update right now, waiting here until complete
|
|
}
|
|
|
|
int32_t InkHUD::LogoApplet::runOnce()
|
|
{
|
|
sendToBackground();
|
|
return OSThread::disable();
|
|
}
|
|
|
|
#endif |