feat(ui): add a home button to the status bar, rename launcher to home
Leaving an app was the app's own responsibility, so one that shipped without an exit could only be escaped with a reset. The bar now paints a back button into its leading square and the firmware treats that rect as home, acting on release so a press sliding into the app cancels. The app it returns to is /apps/home, which is what it is to the user. Card grids in home and settings centre left to right as a unit.
This commit is contained in:
@@ -141,7 +141,7 @@ void registerSys(lua_State* L) {
|
||||
// --- Blocks for the given time.
|
||||
// @param ms integer
|
||||
{"delay", l_sys_delay},
|
||||
// --- Ends this app and returns to the launcher.
|
||||
// --- Ends this app and returns home.
|
||||
{"exit", l_sys_exit},
|
||||
// --- Directory name of the running app, for example "settings".
|
||||
// @return string
|
||||
|
||||
+19
-4
@@ -101,12 +101,14 @@ bool LuaApp::takeFailure() {
|
||||
return value;
|
||||
}
|
||||
|
||||
bool LuaApp::load(const char* path) {
|
||||
bool LuaApp::load(const char* path, bool isHome) {
|
||||
closeState();
|
||||
this->isHome = isHome;
|
||||
homeArmed = false;
|
||||
// Cleared per app: the interval outlives the app that set it, so a ticking app
|
||||
// followed by one that never ticks would keep calling a nil global.
|
||||
tickIntervalMs = 0;
|
||||
// The launcher tap may still be down; swallow that gesture's release.
|
||||
// The tap that launched this app may still be down; swallow that gesture's release.
|
||||
lastTouched = true;
|
||||
ignoreRelease = true;
|
||||
state = luaL_newstate();
|
||||
@@ -177,8 +179,9 @@ void LuaApp::drawStatusBar() {
|
||||
lua_getglobal(state, "__statusbar");
|
||||
lua_getfield(state, -1, "draw");
|
||||
lua_remove(state, -2);
|
||||
lua_pushboolean(state, !isHome); // whether to paint the home button
|
||||
tft.resetViewport(); // the bar paints in panel coordinates, the app does not
|
||||
if (lua_pcall(state, 0, 0, 0) != LUA_OK) {
|
||||
if (lua_pcall(state, 1, 0, 0) != LUA_OK) {
|
||||
Serial.printf("[statusbar] %s\n", luaL_tolstring(state, -1, nullptr));
|
||||
lua_pop(state, 2);
|
||||
barBroken = true;
|
||||
@@ -229,7 +232,19 @@ void LuaApp::loop() {
|
||||
if (touched) {
|
||||
TS_Point p = touch.getPoint();
|
||||
mapTouch(p, lastX, lastY);
|
||||
if (lastY < 0) touched = false; // the bar is the host's, and it takes no input yet
|
||||
}
|
||||
|
||||
// The bar is the host's: apps never see a touch in it, and its one control acts on
|
||||
// release, so a press that slides off into the app cancels like any other button.
|
||||
if (touched && lastY < 0) {
|
||||
homeArmed = homeArmed || inHomeButton();
|
||||
touched = false;
|
||||
} else if (homeArmed) {
|
||||
homeArmed = false;
|
||||
if (lastY < 0) {
|
||||
requestExit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (touched && !lastTouched) {
|
||||
fireTouch("on_touch_down", lastX, lastY);
|
||||
|
||||
+11
-2
@@ -15,10 +15,11 @@ class LuaApp {
|
||||
LuaApp(TFT_eSPI& tft, XPT2046_Touchscreen& touch);
|
||||
~LuaApp();
|
||||
|
||||
bool load(const char* path);
|
||||
// `isHome` marks the app the host falls back to, which needs no way back to itself.
|
||||
bool load(const char* path, bool isHome = false);
|
||||
void loop();
|
||||
|
||||
// Directory name of the running app ("launcher"), for the status bar.
|
||||
// Directory name of the running app ("settings"), for the status bar.
|
||||
const String& name() const { return appName; }
|
||||
|
||||
// Lets an app own the whole panel: no viewport, no status bar. The touch calibration
|
||||
@@ -64,6 +65,8 @@ class LuaApp {
|
||||
bool lastTouched = false;
|
||||
bool ignoreRelease = false;
|
||||
bool fullscreen = false;
|
||||
bool isHome = false;
|
||||
bool homeArmed = false;
|
||||
String appName;
|
||||
// One strike: a bar that failed once fails identically every second, and the serial
|
||||
// log is the only place anyone would see it.
|
||||
@@ -92,6 +95,12 @@ class LuaApp {
|
||||
// and the touch offset.
|
||||
int16_t barInset() const { return (fullscreen || barBroken) ? 0 : barHeight; }
|
||||
|
||||
// The home button is the leading square of the bar, which is what /lib/statusbar.lua
|
||||
// paints into. Touches are in app space, so the bar is above y = 0.
|
||||
bool inHomeButton() const {
|
||||
return !isHome && lastY < 0 && lastY >= -barInset() && lastX >= 0 && lastX < barInset();
|
||||
}
|
||||
|
||||
void registerBindings();
|
||||
bool callGlobal(const char* name, int nargs = 0);
|
||||
bool hasGlobal(const char* name);
|
||||
|
||||
+8
-8
@@ -16,7 +16,7 @@ static constexpr int SD_MOSI = 23;
|
||||
static constexpr int SD_MISO = 19;
|
||||
static constexpr int TOUCH_CS = 33;
|
||||
|
||||
static const char* LAUNCHER = "/apps/launcher/main.lua";
|
||||
static const char* HOME = "/apps/home/main.lua";
|
||||
static constexpr uint32_t ERROR_HOLD_MS = 5000;
|
||||
|
||||
// Arduino's default 8KB loop stack is not enough once a TLS handshake runs inside a
|
||||
@@ -42,7 +42,7 @@ void fallbackScreen(const char* message) {
|
||||
tft.setTextColor(TFT_RED, TFT_WHITE);
|
||||
tft.drawString(message, 10, 10);
|
||||
tft.setTextColor(TFT_BLACK, TFT_WHITE);
|
||||
tft.drawString("expected /apps/launcher/main.lua", 10, 30);
|
||||
tft.drawString("expected /apps/home/main.lua", 10, 30);
|
||||
Serial.printf("halted: %s\n", message);
|
||||
halted = true;
|
||||
}
|
||||
@@ -52,8 +52,8 @@ void startApp(const String& path) {
|
||||
tft.setRotation(settings.rotationIndex()); // apps may have rotated the frame
|
||||
statusbar::apply(tft, 0);
|
||||
Serial.printf("launching %s\n", path.c_str());
|
||||
if (app.load(path.c_str())) return;
|
||||
if (path == LAUNCHER) fallbackScreen("launcher failed to start");
|
||||
if (app.load(path.c_str(), path == HOME)) return;
|
||||
if (path == HOME) fallbackScreen("home failed to start");
|
||||
}
|
||||
|
||||
void setup() {
|
||||
@@ -71,7 +71,7 @@ void setup() {
|
||||
}
|
||||
settings.load(); // absent file keeps the built-in defaults
|
||||
net::begin();
|
||||
startApp(LAUNCHER);
|
||||
startApp(HOME);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -86,11 +86,11 @@ void loop() {
|
||||
app.loop();
|
||||
if (!app.running()) nextApp = app.takePendingLaunch();
|
||||
} else {
|
||||
// An app that died left its message on screen, and the launcher is about to paint
|
||||
// over it. Serial alone is no help to anyone holding the device.
|
||||
// An app that died left its message on screen, and home is about to paint over it.
|
||||
// Serial alone is no help to anyone holding the device.
|
||||
if (app.takeFailure()) delay(ERROR_HOLD_MS);
|
||||
|
||||
String path = nextApp.length() > 0 ? nextApp : String(LAUNCHER);
|
||||
String path = nextApp.length() > 0 ? nextApp : String(HOME);
|
||||
nextApp = "";
|
||||
startApp(path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user