acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

Display.cpp (25563B)

      1 #include "bootScreen.h"
      2 #include "Display.h"
      3 #include "IRC.h"
      4 #include "pins.h"
      5 #include "Speaker.h"
      6 #include "Storage.h"
      7 #include "Utilities.h"
      8 
      9 
     10 bool          infoScreen            = false;
     11 bool          configScreen          = false;
     12 bool          screenOn              = true;
     13 const char*   channel               = "#comms";
     14 unsigned long infoScreenStartTime   = 0;
     15 unsigned long configScreenStartTime = 0;
     16 unsigned long lastStatusUpdateTime  = 0;
     17 unsigned long lastActivityTime      = 0;
     18 String        inputBuffer           = "";
     19 
     20 std::vector<String> lines;
     21 std::vector<bool> mentions;
     22 std::map<String, uint32_t> nickColors;
     23 
     24 TFT_eSPI tft = TFT_eSPI();
     25 
     26 
     27 void addLine(String senderNick, String message, String type, bool mention, uint16_t errorColor, uint16_t reasonColor) {
     28     if (type != "error" && nickColors.find(senderNick) == nickColors.end())
     29         nickColors[senderNick] = generateRandomColor();
     30 
     31     String formattedMessage;
     32 
     33     if (type == "join") {
     34         formattedMessage = "JOIN " + senderNick + " has joined " + String(channel);
     35     } else if (type == "part") {
     36         formattedMessage = "PART " + senderNick + " has EMO-QUIT " + String(channel);
     37     } else if (type == "quit") {
     38         formattedMessage = "QUIT " + senderNick;
     39     } else if (type == "nick") {
     40         int arrowPos = message.indexOf(" -> ");
     41         String oldNick = senderNick;
     42         String newNick = message.substring(arrowPos + 4);
     43         if (nickColors.find(newNick) == nickColors.end()) {
     44             nickColors[newNick] = generateRandomColor();
     45         }
     46         formattedMessage = "NICK " + oldNick + " -> " + newNick;
     47     } else if (type == "kick") {
     48         formattedMessage = "KICK " + senderNick + message;
     49     } else if (type == "mode") {
     50         formattedMessage = "MODE " + message;
     51         tft.setTextColor(TFT_BLUE);
     52     } else if (type == "action") {
     53         formattedMessage = "* " + senderNick + " " + message;
     54     } else if (type == "error") {
     55         formattedMessage = "ERROR " + message;
     56         senderNick = "ERROR";
     57     } else {
     58         formattedMessage = senderNick + ": " + message;
     59     }
     60 
     61     int linesRequired = calculateLinesRequired(formattedMessage);
     62 
     63     while (lines.size() + linesRequired > MAX_LINES) {
     64         lines.erase(lines.begin());
     65         mentions.erase(mentions.begin());
     66     }
     67 
     68     if (type == "error") {
     69         lines.push_back("ERROR " + message);
     70         mentions.push_back(false);
     71     } else {
     72         lines.push_back(formattedMessage);
     73         mentions.push_back(mention);
     74     }
     75 
     76     displayLines();
     77 }
     78 
     79 
     80 int calculateLinesRequired(String message) {
     81     int linesRequired = 1;
     82     int lineWidth = 0;
     83 
     84     for (unsigned int i = 0; i < message.length(); i++) {
     85         char c = message[i];
     86         if (c == '\x03') {
     87             if (i + 1 < message.length() && isdigit(message[i + 1])) {
     88                 i++;
     89                 if (i + 1 < message.length() && isdigit(message[i + 1]))
     90                     i++;
     91             }
     92             if (i + 1 < message.length() && message[i + 1] == ',' && isdigit(message[i + 2])) {
     93                 i += 2;
     94                 if (i + 1 < message.length() && isdigit(message[i + 1]))
     95                     i++;
     96             }
     97         } else if (c != '\x02' && c != '\x0F' && c != '\x1F') {
     98             lineWidth += tft.textWidth(String(c));
     99             if (lineWidth > SCREEN_WIDTH) {
    100                 linesRequired++;
    101                 lineWidth = tft.textWidth(String(c));
    102             }
    103         }
    104     }
    105 
    106     return linesRequired;
    107 }
    108 
    109 
    110 void displayCenteredText(String text) {
    111     tft.fillScreen(TFT_BLACK);
    112     tft.setTextDatum(MC_DATUM);
    113     tft.setTextColor(TFT_GREEN, TFT_BLACK);
    114     tft.drawString(text, SCREEN_WIDTH / 2, (SCREEN_HEIGHT + STATUS_BAR_HEIGHT) / 2);
    115 }
    116 
    117 
    118 void displayInputLine() {
    119     tft.fillRect(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT, SCREEN_WIDTH, INPUT_LINE_HEIGHT, TFT_BLACK);
    120     tft.setCursor(0, SCREEN_HEIGHT - INPUT_LINE_HEIGHT);
    121     tft.setTextColor(TFT_WHITE);
    122     tft.setTextSize(1);
    123 
    124     String displayInput = inputBuffer;
    125     int displayWidth = tft.textWidth(displayInput);
    126     int inputWidth = SCREEN_WIDTH - tft.textWidth("> ");
    127     
    128     while (displayWidth > inputWidth) {
    129         displayInput = displayInput.substring(1);
    130         displayWidth = tft.textWidth(displayInput);
    131     }
    132 
    133     if (inputBuffer.length() >= 510)
    134         tft.setTextColor(TFT_RED);
    135 
    136     tft.print("> " + displayInput);
    137     tft.setTextColor(TFT_WHITE);
    138 }
    139 
    140 
    141 void displayLines() {
    142     tft.fillRect(0, STATUS_BAR_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT, TFT_BLACK);
    143 
    144     int cursorY = STATUS_BAR_HEIGHT;
    145     int totalLinesHeight = 0;
    146     std::vector<int> lineHeights;
    147 
    148     for (const String& line : lines) {
    149         int lineHeight = calculateLinesRequired(line) * CHAR_HEIGHT;
    150         lineHeights.push_back(lineHeight);
    151         totalLinesHeight += lineHeight;
    152     }
    153 
    154     while (totalLinesHeight > SCREEN_HEIGHT - STATUS_BAR_HEIGHT - INPUT_LINE_HEIGHT) {
    155         totalLinesHeight -= lineHeights.front();
    156         lines.erase(lines.begin());
    157         mentions.erase(mentions.begin());
    158         lineHeights.erase(lineHeights.begin());
    159     }
    160 
    161     for (size_t i = 0; i < lines.size(); ++i) {
    162         const String& line = lines[i];
    163         bool mention = mentions[i];
    164 
    165         tft.setCursor(0, cursorY);
    166 
    167         if (line.startsWith("JOIN ")) {
    168             tft.setTextColor(TFT_GREEN);
    169             tft.print("JOIN ");
    170             int startIndex = 5;
    171             int endIndex = line.indexOf(" has joined ");
    172             String senderNick = line.substring(startIndex, endIndex);
    173             tft.setTextColor(nickColors[senderNick]);
    174             tft.print(senderNick);
    175             tft.setTextColor(TFT_WHITE);
    176             tft.print(" has joined ");
    177             tft.setTextColor(TFT_CYAN);
    178             tft.print(channel);
    179             cursorY += CHAR_HEIGHT;
    180         } else if (line.startsWith("PART ")) {
    181             tft.setTextColor(TFT_RED);
    182             tft.print("PART ");
    183             int startIndex = 5;
    184             int endIndex = line.indexOf(" has EMO-QUIT ");
    185             String senderNick = line.substring(startIndex, endIndex);
    186             tft.setTextColor(nickColors[senderNick]);
    187             tft.print(senderNick);
    188             tft.setTextColor(TFT_WHITE);
    189             tft.print(" has EMO-QUIT ");
    190             tft.setTextColor(TFT_CYAN);
    191             tft.print(channel);
    192             cursorY += CHAR_HEIGHT;
    193         } else if (line.startsWith("QUIT ")) {
    194             tft.setTextColor(TFT_RED);
    195             tft.print("QUIT ");
    196             String senderNick = line.substring(5);
    197             tft.setTextColor(nickColors[senderNick]);
    198             tft.print(senderNick);
    199             cursorY += CHAR_HEIGHT;
    200         } else if (line.startsWith("NICK ")) {
    201             tft.setTextColor(TFT_BLUE);
    202             tft.print("NICK ");
    203             int startIndex = 5;
    204             int endIndex = line.indexOf(" -> ");
    205             String oldNick = line.substring(startIndex, endIndex);
    206             String newNick = line.substring(endIndex + 4);
    207             tft.setTextColor(nickColors[oldNick]);
    208             tft.print(oldNick);
    209             tft.setTextColor(TFT_WHITE);
    210             tft.print(" -> ");
    211             tft.setTextColor(nickColors[newNick]);
    212             tft.print(newNick);
    213             cursorY += CHAR_HEIGHT;
    214         } else if (line.startsWith("KICK ")) {
    215             tft.setTextColor(TFT_RED);
    216             tft.print("KICK ");
    217             int startIndex = 5;
    218             int endIndex = line.indexOf(" by ");
    219             String kickedNick = line.substring(startIndex, endIndex);
    220             String kicker = line.substring(endIndex + 4);
    221             tft.setTextColor(nickColors[kickedNick]);
    222             tft.print(kickedNick);
    223             tft.setTextColor(TFT_WHITE);
    224             tft.print(" by ");
    225             tft.setTextColor(nickColors[kicker]);
    226             tft.print(kicker);
    227             cursorY += CHAR_HEIGHT;
    228         } else if (line.startsWith("MODE ")) {
    229             tft.setTextColor(TFT_BLUE);
    230             tft.print("MODE ");
    231             String modeChange = line.substring(5);
    232             tft.setTextColor(TFT_WHITE);
    233             tft.print(modeChange);
    234             cursorY += CHAR_HEIGHT;
    235         } else if (line.startsWith("ERROR ")) {
    236             tft.setTextColor(TFT_RED);
    237             tft.print("ERROR ");
    238             String errorReason = line.substring(6);
    239             tft.setTextColor(TFT_DARKGREY);
    240             tft.print(errorReason);
    241             cursorY += CHAR_HEIGHT;
    242         } else if (line.startsWith("* ")) {
    243             tft.setTextColor(TFT_MAGENTA);
    244             tft.print("* ");
    245             int startIndex = 2;
    246             int endIndex = line.indexOf(' ', startIndex);
    247             String senderNick = line.substring(startIndex, endIndex);
    248             String actionMessage = line.substring(endIndex + 1);
    249             tft.setTextColor(nickColors[senderNick]);
    250             tft.print(senderNick);
    251             tft.setTextColor(TFT_MAGENTA);
    252             tft.print(" " + actionMessage);
    253             cursorY += CHAR_HEIGHT;
    254         } else {
    255             int colonIndex = line.indexOf(':');
    256             String senderNick = line.substring(0, colonIndex);
    257             String message = line.substring(colonIndex + 1);
    258 
    259             tft.setTextColor(nickColors[senderNick]);
    260             tft.print(senderNick);
    261             tft.setTextColor(TFT_WHITE);
    262             cursorY = renderFormattedMessage(":" + message, cursorY, CHAR_HEIGHT, mention);
    263         }
    264     }
    265 
    266     displayInputLine();
    267 }
    268 
    269 
    270 void displayXBM() {
    271     tft.fillScreen(TFT_BLACK);
    272 
    273     int x = (SCREEN_WIDTH - logo_width) / 2;
    274     int y = (SCREEN_HEIGHT - logo_height) / 2;
    275 
    276     tft.drawXBitmap(x, y, logo_bits, logo_width, logo_height, TFT_GREEN);
    277 
    278     unsigned long startTime = millis();
    279     bool wipeInitiated = false;
    280 
    281     while (millis() - startTime < 3000) {
    282         if (getKeyboardInput() == 'w') {
    283             wipeNVS();
    284             tft.fillScreen(TFT_BLACK);
    285             displayCenteredText("NVS WIPED");
    286             delay(2000);
    287             wipeInitiated = true;
    288             break;
    289         }
    290     }
    291 }
    292 
    293 
    294 uint32_t generateRandomColor() {
    295     return tft.color565(random(0, 255), random(0, 255), random(0, 255));
    296 }
    297 
    298 
    299 uint16_t getColorFromCode(int colorCode) {
    300     switch (colorCode) {
    301         case 0: return TFT_WHITE;
    302         case 1: return TFT_BLACK;
    303         case 2: return tft.color565(0, 0, 128); // Dark Blue (Navy)
    304         case 3: return TFT_GREEN;
    305         case 4: return TFT_RED;
    306         case 5: return tft.color565(128, 0, 0); // Brown (Maroon)
    307         case 6: return tft.color565(128, 0, 128); // Purple
    308         case 7: return tft.color565(255, 165, 0); // Orange
    309         case 8: return TFT_YELLOW;
    310         case 9: return tft.color565(144, 238, 144); // Light Green
    311         case 10: return tft.color565(0, 255, 255); // Cyan (Light Blue)
    312         case 11: return tft.color565(224, 255, 255); // Light Cyan (Aqua)
    313         case 12: return TFT_BLUE;
    314         case 13: return tft.color565(255, 192, 203); // Pink (Light Purple)
    315         case 14: return tft.color565(128, 128, 128); // Grey
    316         case 15: return tft.color565(211, 211, 211); // Light Grey
    317         case 16: return 0x4000;
    318         case 17: return 0x4100;
    319         case 18: return 0x4220;
    320         case 19: return 0x3220;
    321         case 20: return 0x0220;
    322         case 21: return 0x0225;
    323         case 22: return 0x0228;
    324         case 23: return 0x0128;
    325         case 24: return 0x0008;
    326         case 25: return 0x2808;
    327         case 26: return 0x4008;
    328         case 27: return 0x4005;
    329         case 28: return 0x7000;
    330         case 29: return 0x71C0;
    331         case 30: return 0x73A0;
    332         case 31: return 0x53A0;
    333         case 32: return 0x03A0;
    334         case 33: return 0x03A9;
    335         case 34: return 0x03AE;
    336         case 35: return 0x020E;
    337         case 36: return 0x000E;
    338         case 37: return 0x480E;
    339         case 38: return 0x700E;
    340         case 39: return 0x7008;
    341         case 40: return 0xB000;
    342         case 41: return 0xB300;
    343         case 42: return 0xB5A0;
    344         case 43: return 0x7DA0;
    345         case 44: return 0x05A0;
    346         case 45: return 0x05AE;
    347         case 46: return 0x05B6;
    348         case 47: return 0x0316;
    349         case 48: return 0x0016;
    350         case 49: return 0x7016;
    351         case 50: return 0xB016;
    352         case 51: return 0xB00D;
    353         case 52: return 0xF800;
    354         case 53: return 0xFC60;
    355         case 54: return 0xFFE0;
    356         case 55: return 0xB7E0;
    357         case 56: return 0x07E0;
    358         case 57: return 0x07F4;
    359         case 58: return 0x07FF;
    360         case 59: return 0x047F;
    361         case 60: return 0x001F;
    362         case 61: return 0xA01F;
    363         case 62: return 0xF81F;
    364         case 63: return 0xF813;
    365         case 64: return 0xFACB;
    366         case 65: return 0xFDAB;
    367         case 66: return 0xFFEE;
    368         case 67: return 0xCFEC;
    369         case 68: return 0x6FED;
    370         case 69: return 0x67F9;
    371         case 70: return 0x6FFF;
    372         case 71: return 0x5DBF;
    373         case 72: return 0x5ADF;
    374         case 73: return 0xC2DF;
    375         case 74: return 0xFB3F;
    376         case 75: return 0xFAD7;
    377         case 76: return 0xFCF3;
    378         case 77: return 0xFE93;
    379         case 78: return 0xFFF3;
    380         case 79: return 0xE7F3;
    381         case 80: return 0x9FF3;
    382         case 81: return 0x9FFB;
    383         case 82: return 0x9FFF;
    384         case 83: return 0x9E9F;
    385         case 84: return 0x9CFF;
    386         case 85: return 0xDCFF;
    387         case 86: return 0xFCFF;
    388         case 87: return 0xFCBA;
    389         case 88: return 0x0000;
    390         case 89: return 0x1082;
    391         case 90: return 0x2945;
    392         case 91: return 0x31A6;
    393         case 92: return 0x4A69;
    394         case 93: return 0x632C;
    395         case 94: return 0x8410;
    396         case 95: return 0x9CF3;
    397         case 96: return 0xBDF7;
    398         case 97: return 0xE71C;
    399         case 98: return 0xFFFF;
    400         default: return TFT_WHITE;
    401     }
    402 }
    403 
    404 
    405 uint16_t getColorFromPercentage(int percentage) {
    406     if      (percentage > 75) return TFT_GREEN;
    407     else if (percentage > 50) return TFT_YELLOW;
    408     else if (percentage > 25) return TFT_ORANGE;
    409     else                      return TFT_RED;
    410 }
    411 
    412 
    413 void handleKeyboardInput(char key) {
    414     lastActivityTime = millis();
    415 
    416     if (!screenOn) {
    417         turnOnScreen();
    418         screenOn = true;
    419         return;
    420     }
    421 
    422     static bool altPressed = false;
    423 
    424     if (key == '\n' || key == '\r') {
    425         if (inputBuffer.startsWith("/nick ")) {
    426             String newNick = inputBuffer.substring(6);
    427             sendIRC("NICK " + newNick);
    428             inputBuffer = "";
    429         } else if (inputBuffer.startsWith("/config")) {
    430             configScreen = true;
    431             configScreenStartTime = millis();
    432             inputBuffer = "";
    433         } else if (inputBuffer.startsWith("/info")) {
    434             infoScreen = true;
    435             infoScreenStartTime = millis();
    436             tft.fillScreen(TFT_BLACK);
    437             printDeviceInfo();
    438             inputBuffer = "";
    439         } else if (inputBuffer.startsWith("/raw ")) {
    440             String rawCommand = inputBuffer.substring(5);
    441             sendIRC(rawCommand);
    442         } else if (inputBuffer.startsWith("/me ")) {
    443             String actionMessage = inputBuffer.substring(4);
    444             sendIRC("PRIVMSG " + String(channel) + " :\001ACTION " + actionMessage + "\001");
    445             addLine(irc_nickname, actionMessage, "action");
    446             inputBuffer = "";
    447         } else {
    448             sendIRC("PRIVMSG " + String(channel) + " :" + inputBuffer);
    449             addLine(irc_nickname, inputBuffer, "message");
    450         }
    451         inputBuffer = "";
    452         displayInputLine();
    453     } else if (key == '\b') {
    454         if (inputBuffer.length() > 0) {
    455             inputBuffer.remove(inputBuffer.length() - 1);
    456             displayInputLine();
    457         }
    458     } else {
    459         inputBuffer += key;
    460         displayInputLine();
    461     }
    462 }
    463 
    464 
    465 void parseAndDisplay(String line) {
    466     int firstSpace = line.indexOf(' ');
    467     int secondSpace = line.indexOf(' ', firstSpace + 1);
    468 
    469     if (firstSpace != -1 && secondSpace != -1) {
    470         String command = line.substring(firstSpace + 1, secondSpace);
    471 
    472         if (command == "PRIVMSG") {
    473             int thirdSpace = line.indexOf(' ', secondSpace + 1);
    474             String target = line.substring(secondSpace + 1, thirdSpace);
    475             if (target == String(channel)) {
    476                 int colonPos = line.indexOf(':', thirdSpace);
    477                 String message = line.substring(colonPos + 1);
    478                 String senderNick = line.substring(1, line.indexOf('!'));
    479                 bool mention = message.indexOf(irc_nickname) != -1;
    480 
    481                 if (mention)
    482                     playNotificationSound();
    483 
    484                 if (message.startsWith(String("\x01") + "ACTION ") && message.endsWith("\x01")) {
    485                     String actionMessage = message.substring(8, message.length() - 1);
    486                     addLine(senderNick, actionMessage, "action");
    487                 } else {
    488                     addLine(senderNick, message, "message", mention);
    489                 }
    490             }
    491         } else if (command == "JOIN" && line.indexOf(channel) != -1) {
    492             String senderNick = line.substring(1, line.indexOf('!'));
    493             addLine(senderNick, " has joined " + String(channel), "join");
    494         } else if (command == "PART" && line.indexOf(channel) != -1) {
    495             String senderNick = line.substring(1, line.indexOf('!'));
    496             addLine(senderNick, " has EMO-QUIT " + String(channel), "part");
    497         } else if (command == "QUIT") {
    498             String senderNick = line.substring(1, line.indexOf('!'));
    499             addLine(senderNick, "", "quit");
    500         } else if (command == "NICK") {
    501             String prefix = line.startsWith(":") ? line.substring(1, firstSpace) : "";
    502             String newNick = line.substring(line.lastIndexOf(':') + 1);
    503 
    504             if (prefix.indexOf('!') == -1) {
    505                 addLine(irc_nickname, " -> " + newNick, "nick");
    506                 irc_nickname = newNick;
    507             } else {
    508                 String oldNick = prefix.substring(0, prefix.indexOf('!'));
    509                 addLine(oldNick, " -> " + newNick, "nick");
    510                 if (oldNick == irc_nickname) {
    511                     irc_nickname = newNick;
    512                 }
    513             }
    514         } else if (command == "KICK") {
    515             int thirdSpace = line.indexOf(' ', secondSpace + 1);
    516             int fourthSpace = line.indexOf(' ', thirdSpace + 1);
    517             String kicker = line.substring(1, line.indexOf('!'));
    518             String kicked = line.substring(thirdSpace + 1, fourthSpace);
    519             addLine(kicked, " by " + kicker, "kick");
    520         } else if (command == "MODE") {
    521             String modeChange = line.substring(secondSpace + 1);
    522             addLine("", modeChange, "mode");
    523         } else if (command == "432") {
    524             addLine("ERROR", "ERR_ERRONEUSNICKNAME", "error", TFT_RED, TFT_DARKGREY);
    525         } else if (command == "433") {
    526             addLine("ERROR", "ERR_NICKNAMEINUSE", "error", TFT_RED, TFT_DARKGREY);
    527             irc_nickname = "ACID_" + String(random(1000, 9999));
    528             sendIRC("NICK " + irc_nickname);
    529         }
    530     }
    531 }
    532 
    533 
    534 int renderFormattedMessage(String message, int cursorY, int lineHeight, bool highlightNick) {
    535     uint16_t fgColor = TFT_WHITE;
    536     uint16_t bgColor = TFT_BLACK;
    537     bool bold = false;
    538     bool underline = false;
    539     bool nickHighlighted = false;
    540 
    541     int nickPos = -1;
    542     if (highlightNick) {
    543         nickPos = message.indexOf(irc_nickname);
    544     }
    545 
    546     for (unsigned int i = 0; i < message.length(); i++) {
    547         char c = message[i];
    548         if (c == '\x02') {
    549             bold = !bold;
    550         } else if (c == '\x1F') {
    551             underline = !underline;
    552         } else if (c == '\x03') {
    553             fgColor = TFT_WHITE;
    554             bgColor = TFT_BLACK;
    555 
    556             if (i + 1 < message.length() && (isdigit(message[i + 1]) || message[i + 1] == ',')) {
    557                 int colorCode = -1;
    558                 if (isdigit(message[i + 1])) {
    559                     colorCode = message[++i] - '0';
    560                     if (i + 1 < message.length() && isdigit(message[i + 1]))
    561                         colorCode = colorCode * 10 + (message[++i] - '0');
    562                 }
    563 
    564                 if (colorCode != -1)
    565                     fgColor = getColorFromCode(colorCode);
    566 
    567                 if (i + 1 < message.length() && message[i + 1] == ',') {
    568                     i++;
    569                     int bgColorCode = -1;
    570                     if (isdigit(message[i + 1])) {
    571                         bgColorCode = message[++i] - '0';
    572                         if (i + 1 < message.length() && isdigit(message[i + 1]))
    573                             bgColorCode = bgColorCode * 10 + (message[++i] - '0');
    574                     }
    575 
    576                     if (bgColorCode != -1)
    577                         bgColor = getColorFromCode(bgColorCode);
    578 
    579                 }
    580 
    581                 tft.setTextColor(fgColor, bgColor);
    582             }
    583         } else if (c == '\x0F') {
    584             fgColor = TFT_WHITE;
    585             bgColor = TFT_BLACK;
    586             bold = false;
    587             underline = false;
    588             tft.setTextColor(fgColor, bgColor);
    589             tft.setTextFont(1);
    590         } else {
    591             if (highlightNick && !nickHighlighted && nickPos != -1 && i == nickPos) {
    592                 tft.setTextColor(TFT_YELLOW, bgColor);
    593                 for (char nc : irc_nickname) {
    594                     tft.print(nc);
    595                     i++;
    596                 }
    597                 i--;
    598                 tft.setTextColor(TFT_WHITE, bgColor);
    599                 nickHighlighted = true;
    600             } else {
    601                 if (tft.getCursorX() + tft.textWidth(String(c)) > SCREEN_WIDTH) {
    602                     cursorY += lineHeight;
    603                     tft.setCursor(0, cursorY);
    604                 }
    605                 if (c == ' ') {
    606                     int spaceWidth = tft.textWidth(" ");
    607                     tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
    608                     tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
    609                 } else {
    610                     tft.fillRect(tft.getCursorX(), tft.getCursorY(), tft.textWidth(String(c)), lineHeight, bgColor);
    611                     tft.setTextColor(fgColor, bgColor);
    612                     tft.print(c);
    613                 }
    614             }
    615         }
    616     }
    617 
    618     if (message.endsWith(" ")) {
    619         int trailingSpaces = 0;
    620         for (int i = message.length() - 1; i >= 0 && message[i] == ' '; i--) {
    621             trailingSpaces++;
    622         }
    623         for (int i = 0; i < trailingSpaces; i++) {
    624             int spaceWidth = tft.textWidth(" ");
    625             tft.fillRect(tft.getCursorX(), tft.getCursorY(), spaceWidth, lineHeight, bgColor);
    626             tft.setCursor(tft.getCursorX() + spaceWidth, tft.getCursorY());
    627         }
    628     }
    629 
    630     cursorY += lineHeight;
    631     return cursorY;
    632 }
    633 
    634 
    635 void setupScreen() {
    636     pinMode(TFT_BL, OUTPUT);
    637     digitalWrite(TFT_BL, HIGH);
    638     setBrightness(8);
    639     tft.begin();
    640     tft.setRotation(1);
    641     tft.invertDisplay(1);
    642     Serial.println("TFT initialized");
    643 }
    644 
    645 
    646 void turnOffScreen() {
    647     Serial.println("Screen turned off");
    648     tft.writecommand(TFT_DISPOFF);
    649     tft.writecommand(TFT_SLPIN);
    650     digitalWrite(TFT_BL, LOW);
    651     screenOn = false;
    652 }
    653 
    654 
    655 void turnOnScreen() {
    656     Serial.println("Screen turned on");
    657     digitalWrite(TFT_BL, HIGH);
    658     tft.writecommand(TFT_SLPOUT);
    659     tft.writecommand(TFT_DISPON);
    660     screenOn = true;
    661 }
    662 
    663 
    664 void updateStatusBar() {
    665     Serial.println("Updating status bar...");
    666     uint16_t darkerGrey = tft.color565(25, 25, 25);
    667     tft.fillRect(0, 0, SCREEN_WIDTH, STATUS_BAR_HEIGHT, darkerGrey);
    668 
    669     struct tm timeinfo;
    670     char timeStr[9];
    671     if (!getLocalTime(&timeinfo)) {
    672         sprintf(timeStr, "12:00 AM");
    673     } else {
    674         int hour = timeinfo.tm_hour;
    675         char ampm[] = "AM";
    676         if (hour == 0) {
    677             hour = 12;
    678         } else if (hour >= 12) {
    679             if (hour > 12)
    680                 hour -= 12;
    681             strcpy(ampm, "PM");
    682         }
    683         sprintf(timeStr, "%02d:%02d %s", hour, timeinfo.tm_min, ampm);
    684     }
    685     tft.setTextDatum(ML_DATUM);
    686     tft.setTextColor(TFT_WHITE, darkerGrey);
    687     tft.drawString(timeStr, 0, STATUS_BAR_HEIGHT / 2);
    688 
    689     char wifiStr[15];
    690     int wifiSignal = 0;
    691     if (WiFi.status() != WL_CONNECTED) {
    692         sprintf(wifiStr, "WiFi: N/A");
    693         tft.setTextColor(TFT_PINK, darkerGrey);
    694         tft.setTextDatum(MR_DATUM);
    695         tft.drawString(wifiStr, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
    696     } else {
    697         int32_t rssi = WiFi.RSSI();
    698         if      (rssi > -50) wifiSignal = 100;
    699         else if (rssi > -60) wifiSignal = 80;
    700         else if (rssi > -70) wifiSignal = 60;
    701         else if (rssi > -80) wifiSignal = 40;
    702         else if (rssi > -90) wifiSignal = 20;
    703         else wifiSignal = 0;
    704 
    705         sprintf(wifiStr, "WiFi: %d%%", wifiSignal);
    706         tft.setTextDatum(MR_DATUM);
    707         tft.setTextColor(TFT_PINK, darkerGrey);
    708         tft.drawString("WiFi:", SCREEN_WIDTH - 120, STATUS_BAR_HEIGHT / 2);
    709         tft.setTextColor(getColorFromPercentage(wifiSignal), darkerGrey);
    710         tft.drawString(wifiStr + 6, SCREEN_WIDTH - 100, STATUS_BAR_HEIGHT / 2);
    711     }
    712 
    713     int batteryLevel = BL.getBatteryChargeLevel();
    714     char batteryStr[15];
    715     sprintf(batteryStr, "Batt: %d%%", batteryLevel);
    716     tft.setTextDatum(MR_DATUM);
    717     tft.setTextColor(TFT_CYAN, darkerGrey);
    718     tft.drawString("Batt:", SCREEN_WIDTH - 40, STATUS_BAR_HEIGHT / 2);
    719     tft.setTextColor(getColorFromPercentage(batteryLevel), darkerGrey);
    720     tft.drawString(batteryStr + 5, SCREEN_WIDTH - 5, STATUS_BAR_HEIGHT / 2);
    721 }