Public Access
Launcher: a grid of icons, with badges; the list stays as a Setting
The Launcher shows the Apps as a 4 x 3 grid of 32 x 32 icons, the selected one's name under it (issue #9). The icons are the website's own, doubled, and two drawn the same way for SSH and the Shell; they are PNGs in assets/icons, turned into arrays by scripts/make_icons.py, which CI checks. The selected tile is the website's: dark blue on cyan, with notched corners. - GridModel (lib/ui, host-tested) holds the selection and the scrolling. - App::badge(): a dot on the tile for unread IRC messages, a Track recording, a Capture running, an SSH session open. - Settings > Launcher: Grid or List. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0162FokPdvY2KsS4NBfwyWPk
This commit is contained in:
@@ -19,7 +19,7 @@ const RowDef kRows[] = {
|
||||
{Row::LongName, Kind::Text, "Long name"}, {Row::ShortName, Kind::Text, "Short name"},
|
||||
{Row::Region, Kind::Choice, "Region"}, {Row::Timezone, Kind::Choice, "Timezone"},
|
||||
{Row::Brightness, Kind::Slider, "Brightness"}, {Row::DimTimeout, Kind::Choice, "Dim after"},
|
||||
{Row::OffTimeout, Kind::Choice, "Screen off after"}, {Row::Sound, Kind::Toggle, "Sound & LED"},
|
||||
{Row::OffTimeout, Kind::Choice, "Screen off after"}, {Row::Launcher, Kind::Toggle, "Launcher"}, {Row::Sound, Kind::Toggle, "Sound & LED"},
|
||||
{Row::Gnss, Kind::Toggle, "GNSS"}, {Row::GnssQuiet, Kind::Toggle, "Pause GNSS for LoRa"},
|
||||
{Row::Coordinates, Kind::Toggle, "Coordinates"},
|
||||
{Row::ProbeMacs, Kind::Toggle, "Probe MACs"}, {Row::Wifi, Kind::Page, "Wi-Fi"},
|
||||
@@ -80,6 +80,7 @@ std::string SettingsMenu::value(int i) const {
|
||||
case Row::Brightness: return std::to_string(settings_.getInt(Setting::Brightness)) + "%";
|
||||
case Row::DimTimeout: return formatSeconds(settings_.getInt(Setting::DimTimeoutS));
|
||||
case Row::OffTimeout: return formatSeconds(settings_.getInt(Setting::OffTimeoutS));
|
||||
case Row::Launcher: return settings_.getBool(Setting::LauncherList) ? "List" : "Grid";
|
||||
case Row::Sound: return settings_.getBool(Setting::Sound) ? "On" : "Off";
|
||||
case Row::Gnss: return settings_.getBool(Setting::GnssEnabled) ? "On" : "Off";
|
||||
case Row::GnssQuiet: return settings_.getBool(Setting::GnssQuietForLora) ? "On" : "Off";
|
||||
@@ -129,6 +130,7 @@ std::string SettingsMenu::choose(int i, int c) {
|
||||
}
|
||||
|
||||
void SettingsMenu::toggle(int i) {
|
||||
if (row(i) == Row::Launcher) settings_.setBool(Setting::LauncherList, !settings_.getBool(Setting::LauncherList));
|
||||
if (row(i) == Row::Sound) settings_.setBool(Setting::Sound, !settings_.getBool(Setting::Sound));
|
||||
if (row(i) == Row::Gnss) settings_.setBool(Setting::GnssEnabled, !settings_.getBool(Setting::GnssEnabled));
|
||||
if (row(i) == Row::GnssQuiet) settings_.setBool(Setting::GnssQuietForLora, !settings_.getBool(Setting::GnssQuietForLora));
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace roro {
|
||||
// values, choice lists and validation messages. Rendering and navigation live in the App.
|
||||
class SettingsMenu {
|
||||
public:
|
||||
enum class Row { LongName, ShortName, Region, Timezone, Brightness, DimTimeout, OffTimeout, Sound, Gnss, GnssQuiet, Coordinates, ProbeMacs, Wifi, CheckUpdates, Firmware, Vpn, DebugConsole, About };
|
||||
enum class Row { LongName, ShortName, Region, Timezone, Brightness, DimTimeout, OffTimeout, Launcher, Sound, Gnss, GnssQuiet, Coordinates, ProbeMacs, Wifi, CheckUpdates, Firmware, Vpn, DebugConsole, About };
|
||||
enum class Kind { Text, Choice, Toggle, Slider, Page };
|
||||
|
||||
explicit SettingsMenu(Settings& settings) : settings_(settings) {}
|
||||
|
||||
@@ -50,6 +50,10 @@ class App {
|
||||
virtual bool retainsContent() const { return false; }
|
||||
virtual void contentLost() {}
|
||||
|
||||
// True while something goes on in the App that is worth a look, whether it is in front or
|
||||
// not (unread messages, a recording running): the Launcher marks its tile (issue #9).
|
||||
virtual bool badge() const { return false; }
|
||||
|
||||
void requestRedraw() { redraw_ = true; }
|
||||
|
||||
bool consumeRedraw() {
|
||||
|
||||
@@ -48,7 +48,7 @@ inline constexpr KeyHelp kText[] = {
|
||||
|
||||
// launcher: The Launcher
|
||||
inline constexpr KeyHelp kLauncher[] = {
|
||||
{"; .", "up, down"},
|
||||
{"; . , /", "move"},
|
||||
{"Enter", "open the App"},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -12,6 +14,7 @@ struct AppInfo {
|
||||
const char* title;
|
||||
bool hidden; // not listed in the Launcher, but can still be opened
|
||||
App* app;
|
||||
const uint8_t* icon = nullptr; // the Launcher's picture of it (ui/icons.h); none: its first letter
|
||||
};
|
||||
|
||||
// Owns which App is in the foreground and routes keys. Home always returns to the Launcher;
|
||||
|
||||
@@ -53,6 +53,7 @@ const Definition kDefinitions[] = {
|
||||
{"ssh_key", Kind::String, 0, "", 0, 800},
|
||||
{"ssh_public", Kind::String, 0, "", 0, 200},
|
||||
{"ssh_font", Kind::Int, 1, nullptr, 0, 4},
|
||||
{"launcher_list", Kind::Bool, 0, nullptr, 0, 1}, // off: the grid
|
||||
};
|
||||
static_assert(sizeof(kDefinitions) / sizeof(kDefinitions[0]) == static_cast<size_t>(Setting::Count),
|
||||
"every Setting needs a definition");
|
||||
|
||||
@@ -41,6 +41,7 @@ enum class Setting : uint8_t {
|
||||
SshKey, // string: this device's own SSH private key, as OpenSSH writes one: never shown
|
||||
SshPublic, // string: its public half, the line to put in a server's authorized_keys
|
||||
SshFont, // int: the terminal's font, 0 (smallest) to 4
|
||||
LauncherList, // bool: the Launcher is a list of names instead of a grid of icons (issue #9)
|
||||
Count
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "grid_model.h"
|
||||
|
||||
namespace roro {
|
||||
|
||||
void GridModel::setCount(int count) {
|
||||
count_ = count < 0 ? 0 : count;
|
||||
if (count_ == 0) {
|
||||
selected_ = -1;
|
||||
first_ = 0;
|
||||
return;
|
||||
}
|
||||
select(selected_ < 0 ? 0 : selected_);
|
||||
}
|
||||
|
||||
void GridModel::select(int index) {
|
||||
if (count_ == 0) return;
|
||||
selected_ = index < 0 ? 0 : index >= count_ ? count_ - 1 : index;
|
||||
int row = selected_ / cols_;
|
||||
if (row < first_) first_ = row;
|
||||
if (row >= first_ + rows_) first_ = row - rows_ + 1;
|
||||
int maxFirst = rows() > rows_ ? rows() - rows_ : 0;
|
||||
if (first_ > maxFirst) first_ = maxFirst;
|
||||
}
|
||||
|
||||
void GridModel::left() {
|
||||
if (count_) select(selected_ == 0 ? count_ - 1 : selected_ - 1);
|
||||
}
|
||||
|
||||
void GridModel::right() {
|
||||
if (count_) select(selected_ == count_ - 1 ? 0 : selected_ + 1);
|
||||
}
|
||||
|
||||
void GridModel::up() {
|
||||
if (!count_) return;
|
||||
int col = selected_ % cols_, row = selected_ / cols_;
|
||||
row = row == 0 ? rows() - 1 : row - 1;
|
||||
select(row * cols_ + col); // past the end of a short last row: the last item
|
||||
}
|
||||
|
||||
void GridModel::down() {
|
||||
if (!count_) return;
|
||||
int col = selected_ % cols_, row = selected_ / cols_;
|
||||
row = row == rows() - 1 ? 0 : row + 1;
|
||||
select(row * cols_ + col);
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
namespace roro {
|
||||
|
||||
// Selection and scrolling for a grid of `cols` columns showing `visibleRows` rows at a time, filled
|
||||
// row by row. Left and Right go through every item and wrap around; Up and Down stay in the
|
||||
// column and wrap around, landing on the last item where the last row has no such column.
|
||||
class GridModel {
|
||||
public:
|
||||
GridModel(int cols, int visibleRows) : cols_(cols), rows_(visibleRows) {}
|
||||
|
||||
void setCount(int count);
|
||||
int count() const { return count_; }
|
||||
int selected() const { return selected_; } // -1 when empty
|
||||
int cols() const { return cols_; }
|
||||
int rows() const { return count_ ? (count_ - 1) / cols_ + 1 : 0; }
|
||||
int firstVisibleRow() const { return first_; }
|
||||
int visibleRows() const { return rows_; }
|
||||
|
||||
void select(int index);
|
||||
void left();
|
||||
void right();
|
||||
void up();
|
||||
void down();
|
||||
|
||||
private:
|
||||
int cols_, rows_;
|
||||
int count_ = 0;
|
||||
int selected_ = -1;
|
||||
int first_ = 0;
|
||||
};
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user