diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b11239773..f2eb0956f 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -135,6 +135,20 @@ static bool heartbeat = false; #include "graphics/ScreenFonts.h" #include +void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display) +{ + for (int row = 0; row < height; row++) { + uint8_t rowMask = (1 << row); + for (int col = 0; col < width; col++) { + uint8_t colData = pgm_read_byte(&bitmapXBM[col]); + if (colData & rowMask) { + // Note: rows become X, columns become Y after transpose + display->fillRect(x + row * 2, y + col * 2, 2, 2); + } + } + } +} + #define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2) // Check if the display can render a string (detect special chars; emoji) @@ -3138,8 +3152,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver) } static int8_t lastFrameIndex = -1; static uint32_t lastFrameChangeTime = 0; -constexpr uint32_t ICON_DISPLAY_DURATION_MS = 1000; +// constexpr uint32_t ICON_DISPLAY_DURATION_MS = 1250; +constexpr uint32_t ICON_DISPLAY_DURATION_MS = 10250; +// Bottom navigation icons void drawCustomFrameIcons(OLEDDisplay *display, OLEDDisplayUiState *state) { int currentFrame = state->currentFrame; @@ -3151,37 +3167,61 @@ void drawCustomFrameIcons(OLEDDisplay *display, OLEDDisplayUiState *state) } // Only show bar briefly after switching frames - if (millis() - lastFrameChangeTime > ICON_DISPLAY_DURATION_MS) return; + if (millis() - lastFrameChangeTime > ICON_DISPLAY_DURATION_MS) + return; - const int iconSize = 8; - const int spacing = 2; - size_t totalIcons = screen->indicatorIcons.size(); - if (totalIcons == 0) return; + const bool useBigIcons = (SCREEN_WIDTH > 128); + const int iconSize = useBigIcons ? 16 : 8; + const int spacing = useBigIcons ? 8 : 4; + const int bigOffset = useBigIcons ? 1 : 0; - int totalWidth = totalIcons * iconSize + (totalIcons - 1) * spacing; - int xStart = (SCREEN_WIDTH - totalWidth) / 2; - int y = SCREEN_HEIGHT - iconSize - 1; + const size_t totalIcons = screen->indicatorIcons.size(); + if (totalIcons == 0) + return; - // Clear background under icon bar to avoid overlaps + const int totalWidth = totalIcons * iconSize + (totalIcons - 1) * spacing; + const int xStart = (SCREEN_WIDTH - totalWidth) / 2; + const int y = SCREEN_HEIGHT - iconSize - 1; + + // Pre-calculate bounding rect + const int rectX = xStart - 2 - bigOffset; + const int rectWidth = totalWidth + 4 + (bigOffset * 2); + const int rectHeight = iconSize + 6; + + // Clear background and draw border display->setColor(BLACK); - display->fillRect(xStart - 1, y - 2, totalWidth + 2, iconSize + 4); + display->fillRect(rectX + 1, y - 2, rectWidth - 2, rectHeight - 2); display->setColor(WHITE); + display->drawRect(rectX, y - 2, rectWidth, rectHeight); + // Icon drawing loop for (size_t i = 0; i < totalIcons; ++i) { - const uint8_t* icon = screen->indicatorIcons[i]; - int x = xStart + i * (iconSize + spacing); + const uint8_t *icon = screen->indicatorIcons[i]; + const int x = xStart + i * (iconSize + spacing); + const bool isActive = (i == static_cast(currentFrame)); - if (i == static_cast(currentFrame)) { - // Draw white box and invert icon for visibility + if (isActive) { display->setColor(WHITE); - display->fillRect(x - 1, y - 1, iconSize + 2, iconSize + 2); + display->fillRect(x - 2, y - 2, iconSize + 4, iconSize + 4); display->setColor(BLACK); - display->drawXbm(x, y, iconSize, iconSize, icon); - display->setColor(WHITE); + } + + if (useBigIcons) { + drawScaledXBitmap16x16(x, y, 8, 8, icon, display); } else { display->drawXbm(x, y, iconSize, iconSize, icon); } + + if (isActive) { + display->setColor(WHITE); + } } + + // Knock the corners off the square + display->setColor(BLACK); + display->drawRect(rectX, y - 2, 1, 1); + display->drawRect(rectX + rectWidth - 1, y - 2, 1, 1); + display->setColor(WHITE); } void Screen::setup() @@ -3209,17 +3249,17 @@ void Screen::setup() displayWidth = dispdev->width(); displayHeight = dispdev->height(); - ui->setTimePerTransition(0); // Disable animation delays - ui->setIndicatorPosition(BOTTOM); // Not used (indicators disabled below) - ui->setIndicatorDirection(LEFT_RIGHT); // Not used (indicators disabled below) - ui->setFrameAnimation(SLIDE_LEFT); // Used only when indicators are active - ui->disableAllIndicators(); // Disable page indicator dots - ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance + ui->setTimePerTransition(0); // Disable animation delays + ui->setIndicatorPosition(BOTTOM); // Not used (indicators disabled below) + ui->setIndicatorDirection(LEFT_RIGHT); // Not used (indicators disabled below) + ui->setFrameAnimation(SLIDE_LEFT); // Used only when indicators are active + ui->disableAllIndicators(); // Disable page indicator dots + ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance // === Set custom overlay callbacks === static OverlayCallback overlays[] = { - drawFunctionOverlay, // For mute/buzzer modifiers etc. - drawCustomFrameIcons // Custom indicator icons for each frame + drawFunctionOverlay, // For mute/buzzer modifiers etc. + drawCustomFrameIcons // Custom indicator icons for each frame }; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); @@ -3254,14 +3294,14 @@ void Screen::setup() dispdev->mirrorScreen(); #else if (!config.display.flip_screen) { - #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) static_cast(dispdev)->flipScreenVertically(); - #elif defined(USE_ST7789) +#elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); - #else +#else dispdev->flipScreenVertically(); - #endif +#endif } #endif @@ -3271,7 +3311,7 @@ void Screen::setup() snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]); #if ARCH_PORTDUINO - handleSetOn(false); // Ensure proper init for Arduino targets + handleSetOn(false); // Ensure proper init for Arduino targets #endif // === Turn on display and trigger first draw === @@ -3285,17 +3325,13 @@ void Screen::setup() // === Optional touchscreen support === #if ARCH_PORTDUINO && !HAS_TFT if (settingsMap[touchscreenModule]) { - touchScreenImpl1 = new TouchScreenImpl1( - dispdev->getWidth(), dispdev->getHeight(), - static_cast(dispdev)->getTouch - ); + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); } #elif HAS_TOUCHSCREEN - touchScreenImpl1 = new TouchScreenImpl1( - dispdev->getWidth(), dispdev->getHeight(), - static_cast(dispdev)->getTouch - ); + touchScreenImpl1 = + new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch); touchScreenImpl1->init(); #endif @@ -3728,7 +3764,7 @@ void Screen::setFrames(FrameFocus focus) } #endif - fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE + fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE this->frameCount = numframes; // ✅ Save frame count for use in custom overlay LOG_DEBUG("Finished build frames. numframes: %d", numframes); @@ -3736,10 +3772,7 @@ void Screen::setFrames(FrameFocus focus) ui->disableAllIndicators(); // Add function overlay here. This can show when notifications muted, modifier key is active etc - static OverlayCallback overlays[] = { - drawFunctionOverlay, - drawCustomFrameIcons - }; + static OverlayCallback overlays[] = {drawFunctionOverlay, drawCustomFrameIcons}; ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0])); prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list diff --git a/src/graphics/images.h b/src/graphics/images.h index af459faa6..3f7986048 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -258,85 +258,76 @@ static const unsigned char mail[] PROGMEM = { // 📬 Mail / Message const uint8_t icon_mail[] PROGMEM = { + 0b00000000, // (padding) 0b11111111, // ████████ top border 0b10000001, // █ █ sides 0b11000011, // ██ ██ diagonal 0b10100101, // █ █ █ █ inner M 0b10011001, // █ ██ █ inner M 0b10000001, // █ █ sides - 0b11111111, // ████████ bottom - 0b00000000 // (padding) + 0b11111111 // ████████ bottom }; // 📍 GPS Screen / Location Pin -const uint8_t icon_compass[] PROGMEM = { - 0b00011000, // ██ - 0b00111100, // ████ - 0b01100110, // ██ ██ - 0b01000010, // █ █ - 0b01000010, // █ █ - 0b00111100, // ████ - 0b00011000, // ██ - 0b00010000 // █ +const unsigned char icon_compass[] PROGMEM = { + 0x3C, // Row 0: ..####.. + 0x52, // Row 1: .#..#.#. + 0x91, // Row 2: #...#..# + 0x91, // Row 3: #...#..# + 0x91, // Row 4: #...#..# + 0x81, // Row 5: #......# + 0x42, // Row 6: .#....#. + 0x3C // Row 7: ..####.. }; const uint8_t icon_radio[] PROGMEM = { - 0b00111000, // ░███░ - 0b01000100, // █░░░█ - 0b10000010, // █░░░░█ - 0b00010000, // ░░█░ - 0b00010000, // ░░█░ - 0b00111000, // ░███░ - 0b01111100, // █████ - 0b00000000 // ░░░░░ + 0x0F, // Row 0: ####.... + 0x10, // Row 1: ....#... + 0x27, // Row 2: ###..#.. + 0x48, // Row 3: ...#..#. + 0x93, // Row 4: ##..#..# + 0xA4, // Row 5: ..#..#.# + 0xA8, // Row 6: ...#.#.# + 0xA9 // Row 7: #..#.#.# }; -// 🪙 Memory Drum Icon (Barrel shape with cuts on the sides) +// 🪙 Memory Icon const uint8_t icon_memory[] PROGMEM = { - 0b00111100, // ░░████░░ - 0b01111110, // ░██████░ - 0b11100111, // ███░░███ - 0b11100111, // ███░░███ - 0b11100111, // ███░░███ - 0b11100111, // ███░░███ - 0b01111110, // ░██████░ - 0b00111100 // ░░████░░ + 0x24, // Row 0: ..#..#.. + 0x3C, // Row 1: ..####.. + 0xC3, // Row 2: ##....## + 0x5A, // Row 3: .#.##.#. + 0x5A, // Row 4: .#.##.#. + 0xC3, // Row 5: ##....## + 0x3C, // Row 6: ..####.. + 0x24 // Row 7: ..#..#.. }; // 🌐 Wi-Fi -const uint8_t icon_wifi[] PROGMEM = { - 0b00000000, - 0b00011000, - 0b00111100, - 0b01111110, - 0b11011011, - 0b00011000, - 0b00011000, - 0b00000000 -}; +const uint8_t icon_wifi[] PROGMEM = {0b00000000, 0b00011000, 0b00111100, 0b01111110, + 0b11011011, 0b00011000, 0b00011000, 0b00000000}; -// 📄 Paper/List Icon (for DynamicNodeListScreen) const uint8_t icon_nodes[] PROGMEM = { - 0b11111111, // Top edge of paper - 0b10000001, // Left & right margin - 0b10101001, // ••• line - 0b10000001, // - 0b10101001, // ••• line - 0b10000001, // - 0b11111111, // Bottom edge - 0b00000000 // + 0xF9, // Row 0 #..####### + 0x00, // Row 1 + 0xF9, // Row 2 #..####### + 0x00, // Row 3 + 0xF9, // Row 4 #..####### + 0x00, // Row 5 + 0xF9, // Row 6 #..####### + 0x00 // Row 7 }; // ➤ Chevron Triangle Arrow Icon (8x8) const uint8_t icon_list[] PROGMEM = { - 0b00011000, // ░░██░░ - 0b00011100, // ░░███░ - 0b00011110, // ░░████ - 0b11111111, // ██████ - 0b00011110, // ░░████ - 0b00011100, // ░░███░ - 0b00011000, // ░░██░░ - 0b00000000 // ░░░░░░ + 0x10, // Row 0: ...#.... + 0x10, // Row 1: ...#.... + 0x38, // Row 2: ..###... + 0x38, // Row 3: ..###... + 0x7C, // Row 4: .#####.. + 0x6C, // Row 5: .##.##.. + 0xC6, // Row 6: ##...##. + 0x82 // Row 7: #.....#. }; // 📶 Signal Bars Icon (left to right, small to large with spacing) @@ -377,14 +368,14 @@ const uint8_t icon_error[] PROGMEM = { // 🏠 Optimized Home Icon (8x8) const uint8_t icon_home[] PROGMEM = { - 0b00011000, // ██ - 0b00111100, // ████ - 0b01111110, // ██████ - 0b11111111, // ███████ - 0b11000011, // ██ ██ - 0b11011011, // ██ ██ ██ - 0b11011011, // ██ ██ ██ - 0b11111111 // ███████ + 0b00011000, // ██ + 0b00111100, // ████ + 0b01111110, // ██████ + 0b11111111, // ███████ + 0b11000011, // ██ ██ + 0b11011011, // ██ ██ ██ + 0b11011011, // ██ ██ ██ + 0b11111111 // ███████ }; // 🔧 Generic module (gear-like shape)