Files
firmware/src/graphics/niche/InkHUD/Applets/System/BatteryIcon/BatteryIconApplet.cpp
scobert969 7bbfe99fbe Add on-screen keyboard to InkHUD (#9445)
* 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>
2026-01-30 13:35:10 -05:00

103 lines
3.4 KiB
C++

#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./BatteryIconApplet.h"
using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet()
{
alwaysRender = true; // render everytime the screen is updated
// Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon)
bringToForeground();
// Register to our have BatteryIconApplet::onPowerStatusUpdate method called when new power info is available
// This happens whether or not the battery icon feature is enabled
powerStatusObserver.observe(&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
int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *status)
{
// System applets are always active
assert(isActive());
// This method should only receive power statuses
// If we get a different type of status, something has gone weird elsewhere
assert(status->getStatusType() == STATUS_TYPE_POWER);
meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)status;
// Get the new state of charge %, and round to the nearest 10%
uint8_t newSocRounded = ((powerStatus->getBatteryChargePercent() + 5) / 10) * 10;
// 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)
requestUpdate();
// Store the new value
this->socRounded = newSocRounded;
return 0; // Tell Observable to continue informing other observers
}
void InkHUD::BatteryIconApplet::onRender(bool full)
{
// Fill entire tile
// - size of icon controlled by size of tile
int16_t l = 0;
int16_t t = 0;
uint16_t w = width();
int16_t h = height();
// Clear the region beneath the tile
// Most applets are drawing onto an empty frame buffer and don't need to do this
// We do need to do this with the battery though, as it is an "overlay"
fillRect(l, t, w, h, WHITE);
// Vertical centerline
const int16_t m = t + (h / 2);
// =====================
// Draw battery outline
// =====================
// Positive terminal "bump"
const int16_t &bumpL = l;
const uint16_t bumpH = h / 2;
const int16_t bumpT = m - (bumpH / 2);
constexpr uint16_t bumpW = 2;
fillRect(bumpL, bumpT, bumpW, bumpH, BLACK);
// Main body of battery
const int16_t bodyL = bumpL + bumpW;
const int16_t &bodyT = t;
const int16_t &bodyH = h;
const int16_t bodyW = w - bumpW;
drawRect(bodyL, bodyT, bodyW, bodyH, BLACK);
// Erase join between bump and body
drawLine(bodyL, bumpT, bodyL, bumpT + bumpH - 1, WHITE);
// ===================
// Draw battery level
// ===================
constexpr int16_t slicePad = 2;
const int16_t sliceL = bodyL + slicePad;
const int16_t sliceT = bodyT + slicePad;
const uint16_t sliceH = bodyH - (slicePad * 2);
uint16_t sliceW = bodyW - (slicePad * 2);
sliceW = (sliceW * socRounded) / 100; // Apply percentage
hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK);
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
}
#endif