Adding support for InkHUD joystick navigation for the Seeed Wio Tracker L1 E-ink (#8678)

* TwoButtonExtened mirrors TwoButton but added joystick functionality

* basic ui navigation with a joystick

settings->joystick.enabled setting added and SETTINGS_VERSION
incremented by one in InkHUD/Persistence.h

in seeed_wio_tracker_L1_eink/nicheGraphics.h enable joystick and
disable "Next Tile" menu item in

implement prevTile and prevApplet functions in
InkHUD/WindowManager.h,cpp and InkHUD/InkHUD.h,cpp

onStickCenterShort, onStickCenterLong, onStickUp, onStickDown,
onStickLeft, and onStickRight functions added to:
- InkHUD/InkHUD.h,cpp
- InkHUD/Events.h,cpp
- InkHUD/Applet.h

change navigation actions in InkHUD/Events.cpp events based on
whether the joystick is enabled or not

in seeed_wio_tracker_L1_eink/nicheGraphics.h connect joystick events to
the new joystick handler functions

* handle joystick input in NotificationApplet and TipsApplet

Both the joystick center short press and the user button short press can
be used to advance through the Tips applet.

dismiss notifications with any joystick input

* MenuApplet controls
allows menu navigation including a back button

* add AlignStickApplet for aligning the joystick with the screen

add joystick.aligned and joystick.alignment to InkHUD/Persistence.h for
storing alignment status and relative angle

create AlignStick applet that prompts the user for a joystick input and
rotates the controls to align with the screen

AlignStick applet is run after the tips applet if the joystick is
enabled and not aligned

add menu item for opening the AlignStick applet

* update tips applet with joystick controls

* format InkHUD additions

* fix stroke consistency when resizing joystick graphic

* tweak button tips for order consistency

* increase joystick debounce

* fix comments

* remove unnecessary '+'

* remap joystick controls to match standard inkHUD behavior

Input with a joystick now behaves as follows

User Button (joystick center):
- short press in applet -> opens menu
- long press in applet -> opens menu
- short press in menu -> selects
- long press in menu -> selects

Exit Button:
- short press in applet -> switches tile
- long press in applet -> nothing for now
- short press in menu -> closes menu
- long press in menu -> nothing for now

---------

Co-authored-by: scobert <scobert57@gmail.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
This commit is contained in:
zeropt
2025-12-20 12:15:42 -08:00
committed by GitHub
parent d97e38bafc
commit 3371d3372c
20 changed files with 1523 additions and 28 deletions

View File

@@ -88,8 +88,14 @@ class Applet : public GFX
virtual void onForeground() {}
virtual void onBackground() {}
virtual void onShutdown() {}
virtual void onButtonShortPress() {} // (System Applets only)
virtual void onButtonLongPress() {} // (System Applets only)
virtual void onButtonShortPress() {}
virtual void onButtonLongPress() {}
virtual void onExitShort() {}
virtual void onExitLong() {}
virtual void onNavUp() {}
virtual void onNavDown() {}
virtual void onNavLeft() {}
virtual void onNavRight() {}
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification

View File

@@ -0,0 +1,205 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./AlignStickApplet.h"
using namespace NicheGraphics;
InkHUD::AlignStickApplet::AlignStickApplet()
{
if (!settings->joystick.aligned)
bringToForeground();
}
void InkHUD::AlignStickApplet::onRender()
{
setFont(fontMedium);
printAt(0, 0, "Align Joystick:");
setFont(fontSmall);
std::string instructions = "Move joystick in the direction indicated";
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), instructions);
// Size of the region in which the joystick graphic should fit
uint16_t joyXLimit = X(0.8);
uint16_t contentH = fontMedium.lineHeight() * 1.5 + fontSmall.lineHeight() * 1;
if (getTextWidth(instructions) > width())
contentH += fontSmall.lineHeight();
uint16_t freeY = height() - contentH - fontSmall.lineHeight() * 1.2;
uint16_t joyYLimit = freeY * 0.8;
// Use the shorter of the two
uint16_t joyWidth = joyXLimit < joyYLimit ? joyXLimit : joyYLimit;
// Center the joystick graphic
uint16_t centerX = X(0.5);
uint16_t centerY = contentH + freeY * 0.5;
// Draw joystick graphic
drawStick(centerX, centerY, joyWidth);
setFont(fontSmall);
printAt(X(0.5), Y(1.0) - fontSmall.lineHeight() * 0.2, "Long press to skip", CENTER, BOTTOM);
}
// Draw a scalable joystick graphic
void InkHUD::AlignStickApplet::drawStick(uint16_t centerX, uint16_t centerY, uint16_t width)
{
if (width < 9) // too small to draw
return;
else if (width < 40) { // only draw up arrow
uint16_t chamfer = width < 20 ? 1 : 2;
// Draw filled up arrow
drawDirection(centerX, centerY - width / 4, Direction::UP, width, chamfer, BLACK);
} else { // large enough to draw the full thing
uint16_t chamfer = width < 80 ? 1 : 2;
uint16_t stroke = 3; // pixels
uint16_t arrowW = width * 0.22;
uint16_t hollowW = arrowW - stroke * 2;
// Draw center circle
fillCircle((int16_t)centerX, (int16_t)centerY, (int16_t)(width * 0.2), BLACK);
fillCircle((int16_t)centerX, (int16_t)centerY, (int16_t)(width * 0.2) - stroke, WHITE);
// Draw filled up arrow
drawDirection(centerX, centerY - width / 2, Direction::UP, arrowW, chamfer, BLACK);
// Draw down arrow
drawDirection(centerX, centerY + width / 2, Direction::DOWN, arrowW, chamfer, BLACK);
drawDirection(centerX, centerY + width / 2 - stroke, Direction::DOWN, hollowW, 0, WHITE);
// Draw left arrow
drawDirection(centerX - width / 2, centerY, Direction::LEFT, arrowW, chamfer, BLACK);
drawDirection(centerX - width / 2 + stroke, centerY, Direction::LEFT, hollowW, 0, WHITE);
// Draw right arrow
drawDirection(centerX + width / 2, centerY, Direction::RIGHT, arrowW, chamfer, BLACK);
drawDirection(centerX + width / 2 - stroke, centerY, Direction::RIGHT, hollowW, 0, WHITE);
}
}
// Draw a scalable joystick direction arrow
// a right-triangle with blunted tips
/*
_ <--point
^ / \
| / \
size / \
| / \
v |_________|
*/
void InkHUD::AlignStickApplet::drawDirection(uint16_t pointX, uint16_t pointY, Direction direction, uint16_t size,
uint16_t chamfer, Color color)
{
uint16_t chamferW = chamfer * 2 + 1;
uint16_t triangleW = size - chamferW;
// Draw arrow
switch (direction) {
case Direction::UP:
fillRect(pointX - chamfer, pointY, chamferW, triangleW, color);
fillRect(pointX - chamfer - triangleW, pointY + triangleW, chamferW + triangleW * 2, chamferW, color);
fillTriangle(pointX - chamfer, pointY, pointX - chamfer - triangleW, pointY + triangleW, pointX - chamfer,
pointY + triangleW, color);
fillTriangle(pointX + chamfer, pointY, pointX + chamfer + triangleW, pointY + triangleW, pointX + chamfer,
pointY + triangleW, color);
break;
case Direction::DOWN:
fillRect(pointX - chamfer, pointY - triangleW + 1, chamferW, triangleW, color);
fillRect(pointX - chamfer - triangleW, pointY - size + 1, chamferW + triangleW * 2, chamferW, color);
fillTriangle(pointX - chamfer, pointY, pointX - chamfer - triangleW, pointY - triangleW, pointX - chamfer,
pointY - triangleW, color);
fillTriangle(pointX + chamfer, pointY, pointX + chamfer + triangleW, pointY - triangleW, pointX + chamfer,
pointY - triangleW, color);
break;
case Direction::LEFT:
fillRect(pointX, pointY - chamfer, triangleW, chamferW, color);
fillRect(pointX + triangleW, pointY - chamfer - triangleW, chamferW, chamferW + triangleW * 2, color);
fillTriangle(pointX, pointY - chamfer, pointX + triangleW, pointY - chamfer - triangleW, pointX + triangleW,
pointY - chamfer, color);
fillTriangle(pointX, pointY + chamfer, pointX + triangleW, pointY + chamfer + triangleW, pointX + triangleW,
pointY + chamfer, color);
break;
case Direction::RIGHT:
fillRect(pointX - triangleW + 1, pointY - chamfer, triangleW, chamferW, color);
fillRect(pointX - size + 1, pointY - chamfer - triangleW, chamferW, chamferW + triangleW * 2, color);
fillTriangle(pointX, pointY - chamfer, pointX - triangleW, pointY - chamfer - triangleW, pointX - triangleW,
pointY - chamfer, color);
fillTriangle(pointX, pointY + chamfer, pointX - triangleW, pointY + chamfer + triangleW, pointX - triangleW,
pointY + chamfer, color);
break;
}
}
void InkHUD::AlignStickApplet::onForeground()
{
// 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;
handleInput = true; // Intercept the button input for our applet
}
void InkHUD::AlignStickApplet::onBackground()
{
// 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::AlignStickApplet::onButtonLongPress()
{
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::AlignStickApplet::onExitLong()
{
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::AlignStickApplet::onNavUp()
{
settings->joystick.aligned = true;
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::AlignStickApplet::onNavDown()
{
inkhud->rotateJoystick(2); // 180 deg
settings->joystick.aligned = true;
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::AlignStickApplet::onNavLeft()
{
inkhud->rotateJoystick(3); // 270 deg
settings->joystick.aligned = true;
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::AlignStickApplet::onNavRight()
{
inkhud->rotateJoystick(1); // 90 deg
settings->joystick.aligned = true;
sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
#endif

View File

@@ -0,0 +1,50 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
System Applet for manually aligning the joystick with the screen
should be run at startup if the joystick is enabled
and not aligned to the screen
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
namespace NicheGraphics::InkHUD
{
class AlignStickApplet : public SystemApplet
{
public:
AlignStickApplet();
void onRender() override;
void onForeground() override;
void onBackground() override;
void onButtonLongPress() override;
void onExitLong() override;
void onNavUp() override;
void onNavDown() override;
void onNavLeft() override;
void onNavRight() override;
protected:
enum Direction {
UP,
DOWN,
LEFT,
RIGHT,
};
void drawStick(uint16_t centerX, uint16_t centerY, uint16_t width);
void drawDirection(uint16_t pointX, uint16_t pointY, Direction direction, uint16_t size, uint16_t chamfer, Color color);
};
} // namespace NicheGraphics::InkHUD
#endif

View File

@@ -30,6 +30,7 @@ enum MenuAction {
TOGGLE_AUTOSHOW_APPLET,
SET_RECENTS,
ROTATE,
ALIGN_JOYSTICK,
LAYOUT,
TOGGLE_BATTERY_ICON,
TOGGLE_NOTIFICATIONS,

View File

@@ -178,6 +178,10 @@ void InkHUD::MenuApplet::execute(MenuItem item)
inkhud->rotate();
break;
case ALIGN_JOYSTICK:
inkhud->openAlignStick();
break;
case LAYOUT:
// Todo: smarter incrementing of tile count
settings->userTiles.count++;
@@ -287,14 +291,17 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
items.push_back(MenuItem("Exit", MenuPage::EXIT));
previousPage = MenuPage::EXIT;
break;
case SEND:
populateSendPage();
previousPage = MenuPage::ROOT;
break;
case CANNEDMESSAGE_RECIPIENT:
populateRecipientPage();
previousPage = MenuPage::OPTIONS;
break;
case OPTIONS:
@@ -321,6 +328,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
if (settings->userTiles.maxCount > 1)
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
if (settings->joystick.enabled)
items.push_back(MenuItem("Align Joystick", MenuAction::ALIGN_JOYSTICK, MenuPage::EXIT));
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,
@@ -332,20 +341,24 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
items.push_back(
MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock));
items.push_back(MenuItem("Exit", MenuPage::EXIT));
previousPage = MenuPage::ROOT;
break;
case APPLETS:
populateAppletPage();
items.push_back(MenuItem("Exit", MenuPage::EXIT));
previousPage = MenuPage::OPTIONS;
break;
case AUTOSHOW:
populateAutoshowPage();
items.push_back(MenuItem("Exit", MenuPage::EXIT));
previousPage = MenuPage::OPTIONS;
break;
case RECENTS:
populateRecentsPage();
previousPage = MenuPage::OPTIONS;
break;
case EXIT:
@@ -479,12 +492,21 @@ void InkHUD::MenuApplet::onButtonShortPress()
// Push the auto-close timer back
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
// Move menu cursor to next entry, then update
if (cursorShown)
cursor = (cursor + 1) % items.size();
else
cursorShown = true;
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
if (!settings->joystick.enabled) {
// Move menu cursor to next entry, then update
if (cursorShown)
cursor = (cursor + 1) % items.size();
else
cursorShown = true;
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
} else {
if (cursorShown)
execute(items.at(cursor));
else
showPage(MenuPage::EXIT);
if (!wantsToRender())
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
}
void InkHUD::MenuApplet::onButtonLongPress()
@@ -504,6 +526,62 @@ void InkHUD::MenuApplet::onButtonLongPress()
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onExitShort()
{
// Exit the menu
showPage(MenuPage::EXIT);
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onNavUp()
{
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
// Move menu cursor to previous entry, then update
if (cursor == 0)
cursor = items.size() - 1;
else
cursor--;
if (!cursorShown)
cursorShown = true;
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onNavDown()
{
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
// Move menu cursor to next entry, then update
if (cursorShown)
cursor = (cursor + 1) % items.size();
else
cursorShown = true;
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onNavLeft()
{
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
// Go to the previous menu page
showPage(previousPage);
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
void InkHUD::MenuApplet::onNavRight()
{
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
if (cursorShown)
execute(items.at(cursor));
if (!wantsToRender())
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
}
// Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu
void InkHUD::MenuApplet::populateAppletPage()
{
@@ -796,4 +874,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources()
cm.recipientItems.clear();
}
#endif
#endif

View File

@@ -27,6 +27,11 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
void onExitShort() override;
void onNavUp() override;
void onNavDown() override;
void onNavLeft() override;
void onNavRight() override;
void onRender() override;
void show(Tile *t); // Open the menu, onto a user tile
@@ -52,6 +57,7 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
MenuPage currentPage = MenuPage::ROOT;
MenuPage previousPage = MenuPage::EXIT;
uint8_t cursor = 0; // Which menu item is currently highlighted
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
@@ -97,4 +103,4 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
} // namespace NicheGraphics::InkHUD
#endif
#endif

View File

@@ -153,6 +153,42 @@ void InkHUD::NotificationApplet::onButtonLongPress()
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onExitShort()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onExitLong()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onNavUp()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onNavDown()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onNavLeft()
{
dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
}
void InkHUD::NotificationApplet::onNavRight()
{
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

View File

@@ -31,6 +31,12 @@ class NotificationApplet : public SystemApplet
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
void onExitShort() override;
void onExitLong() override;
void onNavUp() override;
void onNavDown() override;
void onNavLeft() override;
void onNavRight() override;
int onReceiveTextMessage(const meshtastic_MeshPacket *p);

View File

@@ -112,12 +112,21 @@ void InkHUD::TipsApplet::onRender()
setFont(fontSmall);
int16_t cursorY = fontMedium.lineHeight() * 1.5;
printAt(0, cursorY, "User Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- short press: next");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- long press: select / open menu");
cursorY += fontSmall.lineHeight() * 1.5;
if (!settings->joystick.enabled) {
printAt(0, cursorY, "User Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- short press: next");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- long press: select / open menu");
} else {
printAt(0, cursorY, "Joystick");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- open menu / select");
cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "Exit Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- switch tile / close menu");
}
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break;
@@ -127,8 +136,13 @@ void InkHUD::TipsApplet::onRender()
printAt(0, 0, "Tip: Rotation");
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
if (!settings->joystick.enabled) {
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
} else {
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
}
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
@@ -232,4 +246,10 @@ void InkHUD::TipsApplet::onButtonShortPress()
requestUpdate();
}
// Functions the same as the user button in this instance
void InkHUD::TipsApplet::onExitShort()
{
onButtonShortPress();
}
#endif

View File

@@ -36,6 +36,7 @@ class TipsApplet : public SystemApplet
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onExitShort() override;
protected:
void renderWelcome(); // Very first screen of tutorial

View File

@@ -55,10 +55,15 @@ void InkHUD::Events::onButtonShort()
}
// If no system applet is handling input, default behavior instead is to cycle applets
if (consumer)
// or open menu if joystick is enabled
if (consumer) {
consumer->onButtonShortPress();
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
inkhud->nextApplet();
} else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
if (!settings->joystick.enabled)
inkhud->nextApplet();
else
inkhud->openMenu();
}
}
void InkHUD::Events::onButtonLong()
@@ -83,6 +88,156 @@ void InkHUD::Events::onButtonLong()
inkhud->openMenu();
}
void InkHUD::Events::onExitShort()
{
if (settings->joystick.enabled) {
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is change tiles
if (consumer)
consumer->onExitShort();
else if (!dismissedExt) // Don't change tile if this button press silenced the external notification module
inkhud->nextTile();
}
}
void InkHUD::Events::onExitLong()
{
if (settings->joystick.enabled) {
// Audio feedback (via buzzer)
// Slightly longer than playChirp
playBoop();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
if (consumer)
consumer->onExitLong();
}
}
void InkHUD::Events::onNavUp()
{
if (settings->joystick.enabled) {
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
if (consumer)
consumer->onNavUp();
}
}
void InkHUD::Events::onNavDown()
{
if (settings->joystick.enabled) {
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
if (consumer)
consumer->onNavDown();
}
}
void InkHUD::Events::onNavLeft()
{
if (settings->joystick.enabled) {
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is to cycle applets
if (consumer)
consumer->onNavLeft();
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
inkhud->prevApplet();
}
}
void InkHUD::Events::onNavRight()
{
if (settings->joystick.enabled) {
// Audio feedback (via buzzer)
// Short tone
playChirp();
// Cancel any beeping, buzzing, blinking
// Some button handling suppressed if we are dismissing an external notification (see below)
bool dismissedExt = dismissExternalNotification();
// Check which system applet wants to handle the button press (if any)
SystemApplet *consumer = nullptr;
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
consumer = sa;
break;
}
}
// If no system applet is handling input, default behavior instead is to cycle applets
if (consumer)
consumer->onNavRight();
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
inkhud->nextApplet();
}
}
// Callback for deepSleepObserver
// Returns 0 to signal that we agree to sleep now
int InkHUD::Events::beforeDeepSleep(void *unused)

View File

@@ -29,6 +29,12 @@ class Events
void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press
void onExitShort(); // Exit button: short press
void onExitLong(); // Exit button: long press
void onNavUp(); // Navigate up
void onNavDown(); // Navigate down
void onNavLeft(); // Navigate left
void onNavRight(); // Navigate right
int beforeDeepSleep(void *unused); // Prepare for shutdown
int beforeReboot(void *unused); // Prepare for reboot

View File

@@ -80,6 +80,94 @@ void InkHUD::InkHUD::longpress()
events->onButtonLong();
}
// Call this when your exit button gets a short press
void InkHUD::InkHUD::exitShort()
{
events->onExitShort();
}
// Call this when your exit button gets a long press
void InkHUD::InkHUD::exitLong()
{
events->onExitLong();
}
// Call this when your joystick gets an up input
void InkHUD::InkHUD::navUp()
{
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
case 1: // 90 deg
events->onNavLeft();
break;
case 2: // 180 deg
events->onNavDown();
break;
case 3: // 270 deg
events->onNavRight();
break;
default: // 0 deg
events->onNavUp();
break;
}
}
// Call this when your joystick gets a down input
void InkHUD::InkHUD::navDown()
{
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
case 1: // 90 deg
events->onNavRight();
break;
case 2: // 180 deg
events->onNavUp();
break;
case 3: // 270 deg
events->onNavLeft();
break;
default: // 0 deg
events->onNavDown();
break;
}
}
// Call this when your joystick gets a left input
void InkHUD::InkHUD::navLeft()
{
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
case 1: // 90 deg
events->onNavDown();
break;
case 2: // 180 deg
events->onNavRight();
break;
case 3: // 270 deg
events->onNavUp();
break;
default: // 0 deg
events->onNavLeft();
break;
}
}
// Call this when your joystick gets a right input
void InkHUD::InkHUD::navRight()
{
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
case 1: // 90 deg
events->onNavUp();
break;
case 2: // 180 deg
events->onNavLeft();
break;
case 3: // 270 deg
events->onNavDown();
break;
default: // 0 deg
events->onNavRight();
break;
}
}
// Cycle the next user applet to the foreground
// Only activated applets are cycled
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
@@ -88,6 +176,14 @@ void InkHUD::InkHUD::nextApplet()
windowManager->nextApplet();
}
// Cycle the previous user applet to the foreground
// Only activated applets are cycled
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
void InkHUD::InkHUD::prevApplet()
{
windowManager->prevApplet();
}
// Show the menu (on the the focused tile)
// The applet previously displayed there will be restored once the menu closes
void InkHUD::InkHUD::openMenu()
@@ -95,6 +191,12 @@ void InkHUD::InkHUD::openMenu()
windowManager->openMenu();
}
// Bring AlignStick applet to the foreground
void InkHUD::InkHUD::openAlignStick()
{
windowManager->openAlignStick();
}
// In layouts where multiple applets are shown at once, change which tile is focused
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
void InkHUD::InkHUD::nextTile()
@@ -102,12 +204,26 @@ void InkHUD::InkHUD::nextTile()
windowManager->nextTile();
}
// In layouts where multiple applets are shown at once, change which tile is focused
// The focused tile in the one which cycles applets on button short press, and displays menu on long press
void InkHUD::InkHUD::prevTile()
{
windowManager->prevTile();
}
// Rotate the display image by 90 degrees
void InkHUD::InkHUD::rotate()
{
windowManager->rotate();
}
// rotate the joystick in 90 degree increments
void InkHUD::InkHUD::rotateJoystick(uint8_t angle)
{
persistence->settings.joystick.alignment += angle;
persistence->settings.joystick.alignment %= 4;
}
// Show / hide the battery indicator in top-right
void InkHUD::InkHUD::toggleBatteryIcon()
{

View File

@@ -55,15 +55,25 @@ class InkHUD
void shortpress();
void longpress();
void exitShort();
void exitLong();
void navUp();
void navDown();
void navLeft();
void navRight();
// Trigger UI changes
// - called by various InkHUD components
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h
void nextApplet();
void prevApplet();
void openMenu();
void openAlignStick();
void nextTile();
void prevTile();
void rotate();
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
void toggleBatteryIcon();
// Updating the display

View File

@@ -29,7 +29,7 @@ class Persistence
// Used to invalidate old settings, if needed
// Version 0 is reserved for testing, and will always load defaults
static constexpr uint32_t SETTINGS_VERSION = 2;
static constexpr uint32_t SETTINGS_VERSION = 3;
struct Settings {
struct Meta {
@@ -96,6 +96,19 @@ class Persistence
bool safeShutdownSeen = false;
} tips;
// Joystick settings for enabling and aligning to the screen
struct Joystick {
// Modifies the UI for joystick use
bool enabled = false;
// gets set to true when AlignStick applet is completed
bool aligned = false;
// Rotation of the joystick
// Multiples of 90 degrees clockwise
uint8_t alignment = 0;
} joystick;
// Rotation of the display
// Multiples of 90 degrees clockwise
// Most commonly: rotation is 0 when flex connector is oriented below display

View File

@@ -2,6 +2,7 @@
#include "./WindowManager.h"
#include "./Applets/System/AlignStick/AlignStickApplet.h"
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
#include "./Applets/System/Logo/LogoApplet.h"
#include "./Applets/System/Menu/MenuApplet.h"
@@ -98,6 +99,38 @@ void InkHUD::WindowManager::nextTile()
userTiles.at(settings->userTiles.focused)->requestHighlight();
}
// Focus on a different tile but decrement index
void InkHUD::WindowManager::prevTile()
{
// Close the menu applet if open
// We don't *really* want to do this, but it simplifies handling *a lot*
MenuApplet *menu = (MenuApplet *)inkhud->getSystemApplet("Menu");
bool menuWasOpen = false;
if (menu->isForeground()) {
menu->sendToBackground();
menuWasOpen = true;
}
// Swap to next tile
if (settings->userTiles.focused == 0)
settings->userTiles.focused = settings->userTiles.count - 1;
else
settings->userTiles.focused--;
// Make sure that we don't get stuck on the placeholder tile
refocusTile();
if (menuWasOpen)
menu->show(userTiles.at(settings->userTiles.focused));
// Ask the tile to draw an indicator showing which tile is now focused
// Requests a render
// We only draw this indicator if the device uses an aux button to switch tiles.
// Assume aux button is used to switch tiles if the "next tile" menu item is hidden
if (!settings->optionalMenuItems.nextTile)
userTiles.at(settings->userTiles.focused)->requestHighlight();
}
// Show the menu (on the the focused tile)
// The applet previously displayed there will be restored once the menu closes
void InkHUD::WindowManager::openMenu()
@@ -106,6 +139,15 @@ void InkHUD::WindowManager::openMenu()
menu->show(userTiles.at(settings->userTiles.focused));
}
// Bring the AlignStick applet to the foreground
void InkHUD::WindowManager::openAlignStick()
{
if (settings->joystick.enabled) {
AlignStickApplet *alignStick = (AlignStickApplet *)inkhud->getSystemApplet("AlignStick");
alignStick->bringToForeground();
}
}
// On the currently focussed tile: cycle to the next available user applet
// Applets available for this must be activated, and not already displayed on another tile
void InkHUD::WindowManager::nextApplet()
@@ -155,6 +197,59 @@ void InkHUD::WindowManager::nextApplet()
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
}
// On the currently focussed tile: cycle to the previous available user applet
// Applets available for this must be activated, and not already displayed on another tile
void InkHUD::WindowManager::prevApplet()
{
Tile *t = userTiles.at(settings->userTiles.focused);
// Abort if zero applets available
// nullptr means WindowManager::refocusTile determined that there were no available applets
if (!t->getAssignedApplet())
return;
// Find the index of the applet currently shown on the tile
uint8_t appletIndex = -1;
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
if (inkhud->userApplets.at(i) == t->getAssignedApplet()) {
appletIndex = i;
break;
}
}
// Confirm that we did find the applet
assert(appletIndex != (uint8_t)-1);
// Iterate forward through the WindowManager::applets, looking for the previous valid applet
Applet *prevValidApplet = nullptr;
for (uint8_t i = 1; i < inkhud->userApplets.size(); i++) {
uint8_t newAppletIndex = 0;
if (i > appletIndex)
newAppletIndex = inkhud->userApplets.size() + appletIndex - i;
else
newAppletIndex = (appletIndex - i);
Applet *a = inkhud->userApplets.at(newAppletIndex);
// Looking for an applet which is active (enabled by user), but currently in background
if (a->isActive() && !a->isForeground()) {
prevValidApplet = a;
settings->userTiles.displayedUserApplet[settings->userTiles.focused] =
newAppletIndex; // Remember this setting between boots!
break;
}
}
// Confirm that we found another applet
if (!prevValidApplet)
return;
// Hide old applet, show new applet
t->getAssignedApplet()->sendToBackground();
t->assignApplet(prevValidApplet);
prevValidApplet->bringToForeground();
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
}
// Rotate the display image by 90 degrees
void InkHUD::WindowManager::rotate()
{
@@ -338,6 +433,8 @@ void InkHUD::WindowManager::createSystemApplets()
addSystemApplet("Logo", new LogoApplet, new Tile);
addSystemApplet("Pairing", new PairingApplet, new Tile);
addSystemApplet("Tips", new TipsApplet, new Tile);
if (settings->joystick.enabled)
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
addSystemApplet("Menu", new MenuApplet, nullptr);
@@ -360,6 +457,8 @@ void InkHUD::WindowManager::placeSystemTiles()
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
if (settings->joystick.enabled)
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);

View File

@@ -28,8 +28,11 @@ class WindowManager
// - call these to make stuff change
void nextTile();
void prevTile();
void openMenu();
void openAlignStick();
void nextApplet();
void prevApplet();
void rotate();
void toggleBatteryIcon();