mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-04-29 10:26:52 -07:00
feat: Lyra screens (#732)
## Summary Implements Lyra theme for some more Crosspoint screens:       ## Additional Context - A bit of refactoring for list scrolling logic --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**NO**_ --------- Co-authored-by: Dave Allie <dave@daveallie.com>
This commit is contained in:
co-authored by
Dave Allie
parent
c6ddc5d6a0
commit
e7ee6ff05e
@@ -334,7 +334,9 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
|
||||
return;
|
||||
}
|
||||
|
||||
const int maxRadius = std::min({cornerRadius, width / 2, height / 2});
|
||||
// Assume if we're not rounding all corners then we are only rounding one side
|
||||
const int roundedSides = (!roundTopLeft || !roundTopRight || !roundBottomLeft || !roundBottomRight) ? 1 : 2;
|
||||
const int maxRadius = std::min({cornerRadius, width / roundedSides, height / roundedSides});
|
||||
if (maxRadius <= 0) {
|
||||
fillRectDither(x, y, width, height, color);
|
||||
return;
|
||||
@@ -345,10 +347,16 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
|
||||
fillRectDither(x + maxRadius + 1, y, horizontalWidth - 2, height, color);
|
||||
}
|
||||
|
||||
const int verticalHeight = height - 2 * maxRadius - 2;
|
||||
if (verticalHeight > 0) {
|
||||
fillRectDither(x, y + maxRadius + 1, maxRadius + 1, verticalHeight, color);
|
||||
fillRectDither(x + width - maxRadius - 1, y + maxRadius + 1, maxRadius + 1, verticalHeight, color);
|
||||
const int leftFillTop = y + (roundTopLeft ? (maxRadius + 1) : 0);
|
||||
const int leftFillBottom = y + height - 1 - (roundBottomLeft ? (maxRadius + 1) : 0);
|
||||
if (leftFillBottom >= leftFillTop) {
|
||||
fillRectDither(x, leftFillTop, maxRadius + 1, leftFillBottom - leftFillTop + 1, color);
|
||||
}
|
||||
|
||||
const int rightFillTop = y + (roundTopRight ? (maxRadius + 1) : 0);
|
||||
const int rightFillBottom = y + height - 1 - (roundBottomRight ? (maxRadius + 1) : 0);
|
||||
if (rightFillBottom >= rightFillTop) {
|
||||
fillRectDither(x + width - maxRadius - 1, rightFillTop, maxRadius + 1, rightFillBottom - rightFillTop + 1, color);
|
||||
}
|
||||
|
||||
auto fillArcTemplated = [this](int maxRadius, int cx, int cy, int xDir, int yDir, Color color) {
|
||||
@@ -372,26 +380,18 @@ void GfxRenderer::fillRoundedRect(const int x, const int y, const int width, con
|
||||
|
||||
if (roundTopLeft) {
|
||||
fillArcTemplated(maxRadius, x + maxRadius, y + maxRadius, -1, -1, color);
|
||||
} else {
|
||||
fillRectDither(x, y, maxRadius + 1, maxRadius + 1, color);
|
||||
}
|
||||
|
||||
if (roundTopRight) {
|
||||
fillArcTemplated(maxRadius, x + width - maxRadius - 1, y + maxRadius, 1, -1, color);
|
||||
} else {
|
||||
fillRectDither(x + width - maxRadius - 1, y, maxRadius + 1, maxRadius + 1, color);
|
||||
}
|
||||
|
||||
if (roundBottomRight) {
|
||||
fillArcTemplated(maxRadius, x + width - maxRadius - 1, y + height - maxRadius - 1, 1, 1, color);
|
||||
} else {
|
||||
fillRectDither(x + width - maxRadius - 1, y + height - maxRadius - 1, maxRadius + 1, maxRadius + 1, color);
|
||||
}
|
||||
|
||||
if (roundBottomLeft) {
|
||||
fillArcTemplated(maxRadius, x + maxRadius, y + height - maxRadius - 1, -1, 1, color);
|
||||
} else {
|
||||
fillRectDither(x, y + height - maxRadius - 1, maxRadius + 1, maxRadius + 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@ enum class StrId : uint16_t {
|
||||
STR_UI_THEME,
|
||||
STR_THEME_CLASSIC,
|
||||
STR_THEME_LYRA,
|
||||
STR_THEME_LYRA_EXTENDED,
|
||||
STR_SUNLIGHT_FADING_FIX,
|
||||
STR_REMAP_FRONT_BUTTONS,
|
||||
STR_OPDS_BROWSER,
|
||||
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Plná s pruhem kapitol"
|
||||
STR_UI_THEME: "Šablona rozhraní"
|
||||
STR_THEME_CLASSIC: "Klasická"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Oprava blednutí na slunci"
|
||||
STR_REMAP_FRONT_BUTTONS: "Přemapovat přední tlačítka"
|
||||
STR_OPDS_BROWSER: "Prohlížeč OPDS"
|
||||
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Full w/ Chapter Bar"
|
||||
STR_UI_THEME: "UI Theme"
|
||||
STR_THEME_CLASSIC: "Classic"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Sunlight Fading Fix"
|
||||
STR_REMAP_FRONT_BUTTONS: "Remap Front Buttons"
|
||||
STR_OPDS_BROWSER: "OPDS Browser"
|
||||
|
||||
@@ -48,7 +48,7 @@ STR_HOW_CONNECT: "Comment voulez-vous vous connecter ?"
|
||||
STR_JOIN_NETWORK: "Connexion à un réseau"
|
||||
STR_CREATE_HOTSPOT: "Créer un point d’accès"
|
||||
STR_JOIN_DESC: "Se connecter à un réseau WiFi existant"
|
||||
STR_HOTSPOT_DESC: "Créer un réseau WiFi accessible depuis d’autres appareils"
|
||||
STR_HOTSPOT_DESC: "Créer un réseau WiFi accessible sur d’autres appareils"
|
||||
STR_STARTING_HOTSPOT: "Création du point d’accès en cours…"
|
||||
STR_HOTSPOT_MODE: "Mode point d’accès"
|
||||
STR_CONNECT_WIFI_HINT: "Connectez un appareil à ce réseau WiFi"
|
||||
@@ -81,7 +81,7 @@ STR_CALIBRE_TRANSFER_INTERRUPTED: "Transfert interrompu"
|
||||
STR_CALIBRE_INSTRUCTION_1: "1) Installer le plugin CrossPoint Reader"
|
||||
STR_CALIBRE_INSTRUCTION_2: "2) Se connecter au même réseau WiFi"
|
||||
STR_CALIBRE_INSTRUCTION_3: "3) Dans Calibre : ‘Envoyer vers l’appareil’"
|
||||
STR_CALIBRE_INSTRUCTION_4: "“Gardez cet écran ouvert pendant le transfert”"
|
||||
STR_CALIBRE_INSTRUCTION_4: "4) Gardez cet écran ouvert pendant le transfert"
|
||||
STR_CAT_DISPLAY: "Affichage"
|
||||
STR_CAT_READER: "Lecteur"
|
||||
STR_CAT_CONTROLS: "Commandes"
|
||||
@@ -215,7 +215,7 @@ STR_NO_SERVER_URL: "Aucune URL serveur configurée"
|
||||
STR_FETCH_FEED_FAILED: "Échec du téléchargement du flux"
|
||||
STR_PARSE_FEED_FAILED: "Échec de l’analyse du flux"
|
||||
STR_NETWORK_PREFIX: "Réseau : "
|
||||
STR_IP_ADDRESS_PREFIX: "Adresse IP : "
|
||||
STR_IP_ADDRESS_PREFIX: "IP : "
|
||||
STR_SCAN_QR_WIFI_HINT: "or scan QR code with your phone to connect to Wifi."
|
||||
STR_ERROR_GENERAL_FAILURE: "Erreur : Échec général"
|
||||
STR_ERROR_NETWORK_NOT_FOUND: "Erreur : Réseau introuvable"
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Complète + barre chapitre"
|
||||
STR_UI_THEME: "Thème de l’interface"
|
||||
STR_THEME_CLASSIC: "Classique"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Amélioration de la lisibilité au soleil"
|
||||
STR_REMAP_FRONT_BUTTONS: "Réassigner les boutons avant"
|
||||
STR_OPDS_BROWSER: "Navigateur OPDS"
|
||||
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Komplett + Kapitel"
|
||||
STR_UI_THEME: "System-Design"
|
||||
STR_THEME_CLASSIC: "Klassisch"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Anti-Verblassen"
|
||||
STR_REMAP_FRONT_BUTTONS: "Vordere Tasten belegen"
|
||||
STR_OPDS_BROWSER: "OPDS-Browser"
|
||||
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Completa c/ barra capítulo"
|
||||
STR_UI_THEME: "Tema da interface"
|
||||
STR_THEME_CLASSIC: "Clássico"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Ajuste desbotamento ao sol"
|
||||
STR_REMAP_FRONT_BUTTONS: "Remapear botões frontais"
|
||||
STR_OPDS_BROWSER: "Navegador OPDS"
|
||||
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Полная + шкала главы"
|
||||
STR_UI_THEME: "Тема интерфейса"
|
||||
STR_THEME_CLASSIC: "Классическая"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Компенсация выцветания"
|
||||
STR_REMAP_FRONT_BUTTONS: "Переназначить передние кнопки"
|
||||
STR_OPDS_BROWSER: "OPDS браузер"
|
||||
|
||||
@@ -255,7 +255,8 @@ STR_STATUS_BAR_BOOK_ONLY: "Solo progreso del libro"
|
||||
STR_STATUS_BAR_FULL_CHAPTER: "Completa con progreso de capítulos"
|
||||
STR_UI_THEME: "Estilo de pantalla"
|
||||
STR_THEME_CLASSIC: "Clásico"
|
||||
STR_THEME_LYRA: "LYRA"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Corrección de desvastado por sol"
|
||||
STR_REMAP_FRONT_BUTTONS: "Reconfigurar botones frontales"
|
||||
STR_OPDS_BROWSER: "Navegador opds"
|
||||
|
||||
@@ -256,6 +256,7 @@ STR_STATUS_BAR_FULL_CHAPTER: "Full w/ Kapitellist"
|
||||
STR_UI_THEME: "Användargränssnittstema"
|
||||
STR_THEME_CLASSIC: "Klassisk"
|
||||
STR_THEME_LYRA: "Lyra"
|
||||
STR_THEME_LYRA_EXTENDED: "Lyra Extended"
|
||||
STR_SUNLIGHT_FADING_FIX: "Fix för solskensmattning"
|
||||
STR_REMAP_FRONT_BUTTONS: "Ändra frontknappar"
|
||||
STR_OPDS_BROWSER: "OPDS-webbläsare"
|
||||
|
||||
+1
-1
Submodule open-x4-sdk updated: c8ce3949b3...91e7e2bef7
@@ -120,7 +120,7 @@ class CrossPointSettings {
|
||||
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT };
|
||||
|
||||
// UI Theme
|
||||
enum UI_THEME { CLASSIC = 0, LYRA = 1 };
|
||||
enum UI_THEME { CLASSIC = 0, LYRA = 1, LYRA_3_COVERS = 2 };
|
||||
|
||||
// Sleep screen settings
|
||||
uint8_t sleepScreen = DARK;
|
||||
|
||||
+2
-1
@@ -36,7 +36,8 @@ inline std::vector<SettingInfo> getSettingsList() {
|
||||
{StrId::STR_PAGES_1, StrId::STR_PAGES_5, StrId::STR_PAGES_10, StrId::STR_PAGES_15, StrId::STR_PAGES_30},
|
||||
"refreshFrequency", StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Enum(StrId::STR_UI_THEME, &CrossPointSettings::uiTheme,
|
||||
{StrId::STR_THEME_CLASSIC, StrId::STR_THEME_LYRA}, "uiTheme", StrId::STR_CAT_DISPLAY),
|
||||
{StrId::STR_THEME_CLASSIC, StrId::STR_THEME_LYRA, StrId::STR_THEME_LYRA_EXTENDED}, "uiTheme",
|
||||
StrId::STR_CAT_DISPLAY),
|
||||
SettingInfo::Toggle(StrId::STR_SUNLIGHT_FADING_FIX, &CrossPointSettings::fadingFix, "fadingFix",
|
||||
StrId::STR_CAT_DISPLAY),
|
||||
|
||||
|
||||
@@ -167,7 +167,6 @@ void MyLibraryActivity::loop() {
|
||||
}
|
||||
|
||||
int listSize = static_cast<int>(files.size());
|
||||
|
||||
buttonNavigator.onNextRelease([this, listSize] {
|
||||
selectorIndex = ButtonNavigator::nextIndex(static_cast<int>(selectorIndex), listSize);
|
||||
requestUpdate();
|
||||
|
||||
@@ -169,76 +169,62 @@ void CalibreConnectActivity::loop() {
|
||||
}
|
||||
|
||||
void CalibreConnectActivity::render(Activity::RenderLock&&) {
|
||||
if (state == CalibreConnectState::SERVER_RUNNING) {
|
||||
renderer.clearScreen();
|
||||
renderServerRunning();
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
renderer.clearScreen();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_CALIBRE_WIRELESS));
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height) / 2;
|
||||
|
||||
if (state == CalibreConnectState::SERVER_STARTING) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_CALIBRE_STARTING), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top, tr(STR_CALIBRE_STARTING));
|
||||
} else if (state == CalibreConnectState::ERROR) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, top, tr(STR_CONNECTION_FAILED), true, EpdFontFamily::BOLD);
|
||||
} else if (state == CalibreConnectState::SERVER_RUNNING) {
|
||||
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
|
||||
connectedSSID.c_str(), (std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP).c_str());
|
||||
|
||||
int y = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing * 4;
|
||||
const auto heightText12 = renderer.getTextHeight(UI_12_FONT_ID);
|
||||
renderer.drawText(UI_12_FONT_ID, metrics.contentSidePadding, y, tr(STR_CALIBRE_SETUP), true, EpdFontFamily::BOLD);
|
||||
y += heightText12 + metrics.verticalSpacing * 2;
|
||||
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, y, tr(STR_CALIBRE_INSTRUCTION_1));
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, y + height, tr(STR_CALIBRE_INSTRUCTION_2));
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, y + height * 2, tr(STR_CALIBRE_INSTRUCTION_3));
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, y + height * 3, tr(STR_CALIBRE_INSTRUCTION_4));
|
||||
|
||||
y += height * 3 + metrics.verticalSpacing * 4;
|
||||
renderer.drawText(UI_12_FONT_ID, metrics.contentSidePadding, y, tr(STR_CALIBRE_STATUS), true, EpdFontFamily::BOLD);
|
||||
y += heightText12 + metrics.verticalSpacing * 2;
|
||||
|
||||
if (lastProgressTotal > 0 && lastProgressReceived <= lastProgressTotal) {
|
||||
std::string label = tr(STR_CALIBRE_RECEIVING);
|
||||
if (!currentUploadName.empty()) {
|
||||
label += ": " + currentUploadName;
|
||||
label = renderer.truncatedText(SMALL_FONT_ID, label.c_str(), pageWidth - metrics.contentSidePadding * 2,
|
||||
EpdFontFamily::REGULAR);
|
||||
}
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, y, label.c_str());
|
||||
GUI.drawProgressBar(renderer,
|
||||
Rect{metrics.contentSidePadding, y + height + metrics.verticalSpacing,
|
||||
pageWidth - metrics.contentSidePadding * 2, metrics.progressBarHeight},
|
||||
lastProgressReceived, lastProgressTotal);
|
||||
y += height + metrics.verticalSpacing * 2 + metrics.progressBarHeight;
|
||||
}
|
||||
|
||||
if (lastCompleteAt > 0 && (millis() - lastCompleteAt) < 6000) {
|
||||
std::string msg = std::string(tr(STR_CALIBRE_RECEIVED)) + lastCompleteName;
|
||||
msg = renderer.truncatedText(SMALL_FONT_ID, msg.c_str(), pageWidth - metrics.contentSidePadding * 2,
|
||||
EpdFontFamily::REGULAR);
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, y, msg.c_str());
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
void CalibreConnectActivity::renderServerRunning() const {
|
||||
constexpr int LINE_SPACING = 24;
|
||||
constexpr int SMALL_SPACING = 20;
|
||||
constexpr int SECTION_SPACING = 40;
|
||||
constexpr int TOP_PADDING = 14;
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_CALIBRE_WIRELESS), true, EpdFontFamily::BOLD);
|
||||
|
||||
int y = 55 + TOP_PADDING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_WIFI_NETWORKS), true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, ssidInfo.c_str());
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y + LINE_SPACING,
|
||||
(std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP).c_str());
|
||||
|
||||
y += LINE_SPACING * 2 + SECTION_SPACING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_CALIBRE_SETUP), true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, tr(STR_CALIBRE_INSTRUCTION_1));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING, tr(STR_CALIBRE_INSTRUCTION_2));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 2, tr(STR_CALIBRE_INSTRUCTION_3));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 3, tr(STR_CALIBRE_INSTRUCTION_4));
|
||||
|
||||
y += SMALL_SPACING * 3 + SECTION_SPACING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, tr(STR_CALIBRE_STATUS), true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
if (lastProgressTotal > 0 && lastProgressReceived <= lastProgressTotal) {
|
||||
std::string label = tr(STR_CALIBRE_RECEIVING);
|
||||
if (!currentUploadName.empty()) {
|
||||
label += ": " + currentUploadName;
|
||||
if (label.length() > 34) {
|
||||
label.replace(31, label.length() - 31, "...");
|
||||
}
|
||||
}
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, label.c_str());
|
||||
constexpr int barWidth = 300;
|
||||
constexpr int barHeight = 16;
|
||||
constexpr int barX = (480 - barWidth) / 2;
|
||||
GUI.drawProgressBar(renderer, Rect{barX, y + 22, barWidth, barHeight}, lastProgressReceived, lastProgressTotal);
|
||||
y += 40;
|
||||
}
|
||||
|
||||
if (lastCompleteAt > 0 && (millis() - lastCompleteAt) < 6000) {
|
||||
std::string msg = std::string(tr(STR_CALIBRE_RECEIVED)) + lastCompleteName;
|
||||
if (msg.length() > 36) {
|
||||
msg.replace(33, msg.length() - 33, "...");
|
||||
}
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, msg.c_str());
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", "");
|
||||
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ constexpr const char* AP_PASSWORD = nullptr; // Open network for ease of use
|
||||
constexpr const char* AP_HOSTNAME = "crosspoint";
|
||||
constexpr uint8_t AP_CHANNEL = 1;
|
||||
constexpr uint8_t AP_MAX_CONNECTIONS = 4;
|
||||
constexpr int QR_CODE_WIDTH = 6 * 33;
|
||||
constexpr int QR_CODE_HEIGHT = 200;
|
||||
|
||||
// DNS server for captive portal (redirects all DNS queries to our IP)
|
||||
DNSServer* dnsServer = nullptr;
|
||||
@@ -339,14 +341,24 @@ void CrossPointWebServerActivity::loop() {
|
||||
void CrossPointWebServerActivity::render(Activity::RenderLock&&) {
|
||||
// Only render our own UI when server is running
|
||||
// Subactivities handle their own rendering
|
||||
if (state == WebServerActivityState::SERVER_RUNNING) {
|
||||
renderer.clearScreen();
|
||||
renderServerRunning();
|
||||
renderer.displayBuffer();
|
||||
} else if (state == WebServerActivityState::AP_STARTING) {
|
||||
if (state == WebServerActivityState::SERVER_RUNNING || state == WebServerActivityState::AP_STARTING) {
|
||||
renderer.clearScreen();
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, tr(STR_STARTING_HOTSPOT), true, EpdFontFamily::BOLD);
|
||||
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight},
|
||||
isApMode ? tr(STR_HOTSPOT_MODE) : tr(STR_FILE_TRANSFER), nullptr);
|
||||
|
||||
if (state == WebServerActivityState::SERVER_RUNNING) {
|
||||
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
|
||||
connectedSSID.c_str());
|
||||
renderServerRunning();
|
||||
} else {
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
const auto top = (pageHeight - height) / 2;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_STARTING_HOTSPOT));
|
||||
}
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
}
|
||||
@@ -374,66 +386,70 @@ void drawQRCode(const GfxRenderer& renderer, const int x, const int y, const std
|
||||
}
|
||||
|
||||
void CrossPointWebServerActivity::renderServerRunning() const {
|
||||
// Use consistent line spacing
|
||||
constexpr int LINE_SPACING = 28; // Space between lines
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_FILE_TRANSFER), true, EpdFontFamily::BOLD);
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight},
|
||||
isApMode ? tr(STR_HOTSPOT_MODE) : tr(STR_FILE_TRANSFER), nullptr);
|
||||
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
|
||||
connectedSSID.c_str());
|
||||
|
||||
int startY = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing * 2;
|
||||
int height10 = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
if (isApMode) {
|
||||
// AP mode display - center the content block
|
||||
int startY = 55;
|
||||
// AP mode display
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, startY, tr(STR_CONNECT_WIFI_HINT), true,
|
||||
EpdFontFamily::BOLD);
|
||||
startY += height10 + metrics.verticalSpacing * 2;
|
||||
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, tr(STR_HOTSPOT_MODE), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ssidInfo.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, tr(STR_CONNECT_WIFI_HINT));
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, tr(STR_SCAN_QR_WIFI_HINT));
|
||||
// Show QR code for URL
|
||||
// Show QR code for Wifi
|
||||
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
||||
drawQRCode(renderer, metrics.contentSidePadding, startY, wifiConfig);
|
||||
|
||||
// Show network name
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding + QR_CODE_WIDTH + metrics.verticalSpacing, startY + 80,
|
||||
connectedSSID.c_str());
|
||||
|
||||
startY += QR_CODE_HEIGHT + 2 * metrics.verticalSpacing;
|
||||
|
||||
startY += 6 * 29 + 3 * LINE_SPACING;
|
||||
// Show primary URL (hostname)
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, startY, tr(STR_OPEN_URL_HINT), true,
|
||||
EpdFontFamily::BOLD);
|
||||
startY += height10 + metrics.verticalSpacing * 2;
|
||||
|
||||
std::string hostnameUrl = std::string("http://") + AP_HOSTNAME + ".local/";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
std::string ipUrl = tr(STR_OR_HTTP_PREFIX) + connectedIP + "/";
|
||||
|
||||
// Show QR code for URL
|
||||
drawQRCode(renderer, metrics.contentSidePadding, startY, hostnameUrl);
|
||||
|
||||
// Show IP address as fallback
|
||||
std::string ipUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + connectedIP + "/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ipUrl.c_str());
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, tr(STR_OPEN_URL_HINT));
|
||||
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding + QR_CODE_WIDTH + metrics.verticalSpacing, startY + 80,
|
||||
hostnameUrl.c_str());
|
||||
renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding + QR_CODE_WIDTH + metrics.verticalSpacing, startY + 100,
|
||||
ipUrl.c_str());
|
||||
} else {
|
||||
startY += metrics.verticalSpacing * 2;
|
||||
|
||||
// STA mode display (original behavior)
|
||||
// std::string ipInfo = "IP Address: " + connectedIP;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, tr(STR_OPEN_URL_HINT), true, EpdFontFamily::BOLD);
|
||||
startY += height10;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, tr(STR_SCAN_QR_HINT), true, EpdFontFamily::BOLD);
|
||||
startY += height10 + metrics.verticalSpacing * 2;
|
||||
|
||||
// Show QR code for URL
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, tr(STR_SCAN_QR_HINT));
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
|
||||
} else {
|
||||
// STA mode display (original behavior)
|
||||
const int startY = 65;
|
||||
|
||||
std::string ssidInfo = std::string(tr(STR_NETWORK_PREFIX)) + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
||||
|
||||
std::string ipInfo = std::string(tr(STR_IP_ADDRESS_PREFIX)) + connectedIP;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ipInfo.c_str());
|
||||
std::string webInfo = "http://" + connectedIP + "/";
|
||||
drawQRCode(renderer, (pageWidth - QR_CODE_WIDTH) / 2, startY, webInfo);
|
||||
startY += QR_CODE_HEIGHT + metrics.verticalSpacing * 2;
|
||||
|
||||
// Show web server URL prominently
|
||||
std::string webInfo = "http://" + connectedIP + "/";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, webInfo.c_str(), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, webInfo.c_str(), true);
|
||||
startY += height10 + 5;
|
||||
|
||||
// Also show hostname URL
|
||||
std::string hostnameUrl = std::string(tr(STR_OR_HTTP_PREFIX)) + AP_HOSTNAME + ".local/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, tr(STR_OPEN_URL_HINT));
|
||||
|
||||
// Show QR code for URL
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, tr(STR_SCAN_QR_HINT));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY, hostnameUrl.c_str(), true);
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_EXIT), "", "", "");
|
||||
|
||||
@@ -57,39 +57,24 @@ void NetworkModeSelectionActivity::loop() {
|
||||
void NetworkModeSelectionActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_FILE_TRANSFER), true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw subtitle
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 50, tr(STR_HOW_CONNECT));
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_FILE_TRANSFER));
|
||||
|
||||
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
|
||||
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2;
|
||||
// Menu items and descriptions
|
||||
static constexpr StrId menuItems[MENU_ITEM_COUNT] = {StrId::STR_JOIN_NETWORK, StrId::STR_CALIBRE_WIRELESS,
|
||||
StrId::STR_CREATE_HOTSPOT};
|
||||
static constexpr StrId menuDescs[MENU_ITEM_COUNT] = {StrId::STR_JOIN_DESC, StrId::STR_CALIBRE_DESC,
|
||||
StrId::STR_HOTSPOT_DESC};
|
||||
|
||||
// Draw menu items centered on screen
|
||||
constexpr int itemHeight = 50; // Height for each menu item (including description)
|
||||
const int startY = (pageHeight - (MENU_ITEM_COUNT * itemHeight)) / 2 + 10;
|
||||
|
||||
for (int i = 0; i < MENU_ITEM_COUNT; i++) {
|
||||
const int itemY = startY + i * itemHeight;
|
||||
const bool isSelected = (i == selectedIndex);
|
||||
|
||||
// Draw selection highlight (black fill) for selected item
|
||||
if (isSelected) {
|
||||
renderer.fillRect(20, itemY - 2, pageWidth - 40, itemHeight - 6);
|
||||
}
|
||||
|
||||
// Draw text: black=false (white text) when selected (on black background)
|
||||
// black=true (black text) when not selected (on white background)
|
||||
renderer.drawText(UI_10_FONT_ID, 30, itemY, I18N.get(menuItems[i]), /*black=*/!isSelected);
|
||||
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, I18N.get(menuDescs[i]), /*black=*/!isSelected);
|
||||
}
|
||||
GUI.drawList(
|
||||
renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(MENU_ITEM_COUNT), selectedIndex,
|
||||
[](int index) { return std::string(I18N.get(menuItems[index])); },
|
||||
[](int index) { return std::string(I18N.get(menuDescs[index])); });
|
||||
|
||||
// Draw help text at bottom
|
||||
const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN));
|
||||
|
||||
@@ -149,13 +149,12 @@ void WifiSelectionActivity::processWifiScanResults() {
|
||||
networks.push_back(pair.second);
|
||||
}
|
||||
|
||||
// Sort by signal strength (strongest first)
|
||||
std::sort(networks.begin(), networks.end(),
|
||||
[](const WifiNetworkInfo& a, const WifiNetworkInfo& b) { return a.rssi > b.rssi; });
|
||||
|
||||
// Show networks with PW first
|
||||
// Sort: saved-password networks first, then by signal strength (strongest first)
|
||||
std::sort(networks.begin(), networks.end(), [](const WifiNetworkInfo& a, const WifiNetworkInfo& b) {
|
||||
return a.hasSavedPassword && !b.hasSavedPassword;
|
||||
if (a.hasSavedPassword != b.hasSavedPassword) {
|
||||
return a.hasSavedPassword;
|
||||
}
|
||||
return a.rssi > b.rssi;
|
||||
});
|
||||
|
||||
WiFi.scanDelete();
|
||||
@@ -194,7 +193,6 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
|
||||
"", // No initial text
|
||||
50, // Y position
|
||||
64, // Max password length
|
||||
false, // Show password by default (hard keyboard to use)
|
||||
[this](const std::string& text) {
|
||||
@@ -455,15 +453,12 @@ std::string WifiSelectionActivity::getSignalStrengthIndicator(const int32_t rssi
|
||||
return "||||"; // Excellent
|
||||
}
|
||||
if (rssi >= -60) {
|
||||
return "||| "; // Good
|
||||
return " |||"; // Good
|
||||
}
|
||||
if (rssi >= -70) {
|
||||
return "|| "; // Fair
|
||||
return " ||"; // Fair
|
||||
}
|
||||
if (rssi >= -80) {
|
||||
return "| "; // Weak
|
||||
}
|
||||
return " "; // Very weak
|
||||
return " |"; // Very weak
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::render(Activity::RenderLock&&) {
|
||||
@@ -476,6 +471,18 @@ void WifiSelectionActivity::render(Activity::RenderLock&&) {
|
||||
|
||||
renderer.clearScreen();
|
||||
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
char countStr[32];
|
||||
snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), networks.size());
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_WIFI_NETWORKS),
|
||||
countStr);
|
||||
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
|
||||
cachedMacAddress.c_str());
|
||||
|
||||
switch (state) {
|
||||
case WifiSelectionState::AUTO_CONNECTING:
|
||||
renderConnecting();
|
||||
@@ -507,12 +514,10 @@ void WifiSelectionActivity::render(Activity::RenderLock&&) {
|
||||
}
|
||||
|
||||
void WifiSelectionActivity::renderNetworkList() const {
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_WIFI_NETWORKS), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (networks.empty()) {
|
||||
// No networks found or scan failed
|
||||
const auto height = renderer.getLineHeight(UI_10_FONT_ID);
|
||||
@@ -520,69 +525,21 @@ void WifiSelectionActivity::renderNetworkList() const {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, top, tr(STR_NO_NETWORKS));
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, tr(STR_PRESS_OK_SCAN));
|
||||
} else {
|
||||
// Calculate how many networks we can display
|
||||
constexpr int startY = 60;
|
||||
constexpr int lineHeight = 25;
|
||||
const int maxVisibleNetworks = (pageHeight - startY - 40) / lineHeight;
|
||||
|
||||
// Calculate scroll offset to keep selected item visible
|
||||
int scrollOffset = 0;
|
||||
if (selectedNetworkIndex >= maxVisibleNetworks) {
|
||||
scrollOffset = selectedNetworkIndex - maxVisibleNetworks + 1;
|
||||
}
|
||||
|
||||
// Draw networks
|
||||
int displayIndex = 0;
|
||||
for (size_t i = scrollOffset; i < networks.size() && displayIndex < maxVisibleNetworks; i++, displayIndex++) {
|
||||
const int networkY = startY + displayIndex * lineHeight;
|
||||
const auto& network = networks[i];
|
||||
|
||||
// Draw selection indicator
|
||||
if (static_cast<int>(i) == selectedNetworkIndex) {
|
||||
renderer.drawText(UI_10_FONT_ID, 5, networkY, ">");
|
||||
}
|
||||
|
||||
// Draw network name (truncate if too long)
|
||||
std::string displayName = network.ssid;
|
||||
if (displayName.length() > 33) {
|
||||
displayName.replace(30, displayName.length() - 30, "...");
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, 20, networkY, displayName.c_str());
|
||||
|
||||
// Draw signal strength indicator
|
||||
std::string signalStr = getSignalStrengthIndicator(network.rssi);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 90, networkY, signalStr.c_str());
|
||||
|
||||
// Draw saved indicator (checkmark) for networks with saved passwords
|
||||
if (network.hasSavedPassword) {
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 50, networkY, "+");
|
||||
}
|
||||
|
||||
// Draw lock icon for encrypted networks
|
||||
if (network.isEncrypted) {
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 30, networkY, "*");
|
||||
}
|
||||
}
|
||||
|
||||
// Draw scroll indicators if needed
|
||||
if (scrollOffset > 0) {
|
||||
renderer.drawText(SMALL_FONT_ID, pageWidth - 15, startY - 10, "^");
|
||||
}
|
||||
if (scrollOffset + maxVisibleNetworks < static_cast<int>(networks.size())) {
|
||||
renderer.drawText(SMALL_FONT_ID, pageWidth - 15, startY + maxVisibleNetworks * lineHeight, "v");
|
||||
}
|
||||
|
||||
// Show network count
|
||||
char countStr[64];
|
||||
snprintf(countStr, sizeof(countStr), tr(STR_NETWORKS_FOUND), networks.size());
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr);
|
||||
int contentTop = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing;
|
||||
int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing * 2;
|
||||
GUI.drawList(
|
||||
renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast<int>(networks.size()),
|
||||
selectedNetworkIndex, [this](int index) { return networks[index].ssid; }, nullptr, nullptr,
|
||||
[this](int index) {
|
||||
auto network = networks[index];
|
||||
return std::string(network.hasSavedPassword ? "+ " : "") + (network.isEncrypted ? "* " : "") +
|
||||
getSignalStrengthIndicator(network.rssi);
|
||||
});
|
||||
}
|
||||
|
||||
// Show MAC address above the network count and legend
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str());
|
||||
|
||||
// Draw help text
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, tr(STR_NETWORK_LEGEND));
|
||||
GUI.drawHelpText(renderer,
|
||||
Rect{0, pageHeight - metrics.buttonHintsHeight - metrics.contentSidePadding - 15, pageWidth, 20},
|
||||
tr(STR_NETWORK_LEGEND));
|
||||
|
||||
const bool hasSavedPassword = !networks.empty() && networks[selectedNetworkIndex].hasSavedPassword;
|
||||
const char* forgetLabel = hasSavedPassword ? tr(STR_FORGET_BUTTON) : "";
|
||||
|
||||
@@ -45,7 +45,7 @@ class WifiSelectionActivity final : public ActivityWithSubactivity {
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
WifiSelectionState state = WifiSelectionState::SCANNING;
|
||||
int selectedNetworkIndex = 0;
|
||||
size_t selectedNetworkIndex = 0;
|
||||
std::vector<WifiNetworkInfo> networks;
|
||||
const std::function<void(bool connected)> onComplete;
|
||||
|
||||
|
||||
@@ -95,9 +95,6 @@ void ButtonRemapActivity::loop() {
|
||||
}
|
||||
|
||||
void ButtonRemapActivity::render(Activity::RenderLock&&) {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto labelForHardware = [&](uint8_t hardwareIndex) -> const char* {
|
||||
for (uint8_t i = 0; i < kRoleCount; i++) {
|
||||
if (tempMapping[i] == hardwareIndex) {
|
||||
@@ -107,35 +104,41 @@ void ButtonRemapActivity::render(Activity::RenderLock&&) {
|
||||
return "-";
|
||||
};
|
||||
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, tr(STR_REMAP_FRONT_BUTTONS), true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 40, tr(STR_REMAP_PROMPT));
|
||||
auto metrics = UITheme::getInstance().getMetrics();
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
for (uint8_t i = 0; i < kRoleCount; i++) {
|
||||
const int y = 70 + i * 30;
|
||||
const bool isSelected = (i == currentStep);
|
||||
renderer.clearScreen();
|
||||
|
||||
// Highlight the role that is currently being assigned.
|
||||
if (isSelected) {
|
||||
renderer.fillRect(0, y - 2, pageWidth - 1, 30);
|
||||
}
|
||||
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_REMAP_FRONT_BUTTONS));
|
||||
GUI.drawSubHeader(renderer, Rect{0, metrics.topPadding + metrics.headerHeight, pageWidth, metrics.tabBarHeight},
|
||||
tr(STR_REMAP_PROMPT));
|
||||
|
||||
const char* roleName = getRoleName(i);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, y, roleName, !isSelected);
|
||||
|
||||
// Show currently assigned hardware button (or unassigned).
|
||||
const char* assigned = (tempMapping[i] == kUnassigned) ? tr(STR_UNASSIGNED) : getHardwareName(tempMapping[i]);
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, assigned);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, y, assigned, !isSelected);
|
||||
}
|
||||
int topOffset = metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.verticalSpacing;
|
||||
int contentHeight = pageHeight - topOffset - metrics.buttonHintsHeight - metrics.verticalSpacing;
|
||||
GUI.drawList(
|
||||
renderer, Rect{0, topOffset, pageWidth, contentHeight}, kRoleCount, currentStep,
|
||||
[&](int index) { return getRoleName(static_cast<uint8_t>(index)); }, nullptr, nullptr,
|
||||
[&](int index) {
|
||||
uint8_t assignedButton = tempMapping[static_cast<uint8_t>(index)];
|
||||
return (assignedButton == kUnassigned) ? tr(STR_UNASSIGNED) : getHardwareName(assignedButton);
|
||||
},
|
||||
true);
|
||||
|
||||
// Temporary warning banner for duplicates.
|
||||
if (!errorMessage.empty()) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 210, errorMessage.c_str(), true);
|
||||
GUI.drawHelpText(renderer,
|
||||
Rect{0, pageHeight - metrics.buttonHintsHeight - metrics.contentSidePadding - 15, pageWidth, 20},
|
||||
errorMessage.c_str());
|
||||
}
|
||||
|
||||
// Provide side button actions at the bottom of the screen (split across two lines).
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 250, tr(STR_REMAP_RESET_HINT), true);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 280, tr(STR_REMAP_CANCEL_HINT), true);
|
||||
GUI.drawHelpText(renderer,
|
||||
Rect{0, topOffset + 4 * metrics.listRowHeight + 4 * metrics.verticalSpacing, pageWidth, 20},
|
||||
tr(STR_REMAP_RESET_HINT));
|
||||
GUI.drawHelpText(renderer,
|
||||
Rect{0, topOffset + 4 * metrics.listRowHeight + 5 * metrics.verticalSpacing + 20, pageWidth, 20},
|
||||
tr(STR_REMAP_CANCEL_HINT));
|
||||
|
||||
// Live preview of logical labels under front buttons.
|
||||
// This mirrors the on-device front button order: Back, Confirm, Left, Right.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user