#include "configuration.h" #if HAS_SCREEN #include "ClockRenderer.h" #include "NodeDB.h" #include "UIRenderer.h" #include "configuration.h" #include "gps/GeoCoord.h" #include "gps/RTC.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/emotes.h" #include "graphics/images.h" #include "main.h" #if !MESHTASTIC_EXCLUDE_BLUETOOTH #include "nimble/NimbleBluetooth.h" #endif namespace graphics { namespace ClockRenderer { void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale) { uint16_t segmentWidth = SEGMENT_WIDTH * scale; uint16_t segmentHeight = SEGMENT_HEIGHT * scale; uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8; uint16_t topAndBottomX = x + (4 * scale); uint16_t quarterCellHeight = cellHeight / 4; uint16_t topY = y + quarterCellHeight; uint16_t bottomY = y + (quarterCellHeight * 3); display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight); display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight); } void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale) { // the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of // segment {innerIndex + 1} // e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off. uint8_t numbers[10][7] = { {1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key {0, 1, 1, 0, 0, 0, 0}, // 1 1 {1, 1, 0, 1, 1, 0, 1}, // 2 ___ {1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2 {0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_| {1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3 {1, 0, 1, 1, 1, 1, 1}, // 6 |___| {1, 1, 1, 0, 0, 1, 0}, // 7 {1, 1, 1, 1, 1, 1, 1}, // 8 4 {1, 1, 1, 1, 0, 1, 1}, // 9 }; // the width and height of each segment's central rectangle: // _____________________ // ⋰| (only this part, |⋱ // ⋰ | not including | ⋱ // ⋱ | the triangles | ⋰ // ⋱| on the ends) |⋰ // ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ uint16_t segmentWidth = SEGMENT_WIDTH * scale; uint16_t segmentHeight = SEGMENT_HEIGHT * scale; // segment x and y coordinates uint16_t segmentOneX = x + segmentHeight + 2; uint16_t segmentOneY = y; uint16_t segmentTwoX = segmentOneX + segmentWidth + 2; uint16_t segmentTwoY = segmentOneY + segmentHeight + 2; uint16_t segmentThreeX = segmentTwoX; uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2; uint16_t segmentFourX = segmentOneX; uint16_t segmentFourY = segmentThreeY + segmentWidth + 2; uint16_t segmentFiveX = x; uint16_t segmentFiveY = segmentThreeY; uint16_t segmentSixX = x; uint16_t segmentSixY = segmentTwoY; uint16_t segmentSevenX = segmentOneX; uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2; if (numbers[number][0]) { graphics::ClockRenderer::drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight); } if (numbers[number][1]) { graphics::ClockRenderer::drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight); } if (numbers[number][2]) { graphics::ClockRenderer::drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight); } if (numbers[number][3]) { graphics::ClockRenderer::drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight); } if (numbers[number][4]) { graphics::ClockRenderer::drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight); } if (numbers[number][5]) { graphics::ClockRenderer::drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight); } if (numbers[number][6]) { graphics::ClockRenderer::drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight); } } void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height) { int halfHeight = height / 2; // draw central rectangle display->fillRect(x, y, width, height); // draw end triangles display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight); display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1); } void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height) { int halfHeight = height / 2; // draw central rectangle display->fillRect(x, y, height, width); // draw end triangles display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y); display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight); } /* void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale) { uint16_t segmentWidth = SEGMENT_WIDTH * scale; uint16_t segmentHeight = SEGMENT_HEIGHT * scale; if (digitalMode) { uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2; uint16_t centerX = (x + segmentHeight + 2) + (radius / 2); uint16_t centerY = (y + segmentHeight + 2) + (radius / 2); display->drawCircle(centerX, centerY, radius); display->drawCircle(centerX, centerY, radius + 1); display->drawLine(centerX, centerY, centerX, centerY - radius + 3); display->drawLine(centerX, centerY, centerX + radius - 3, centerY); } else { uint16_t segmentOneX = x + segmentHeight + 2; uint16_t segmentOneY = y; uint16_t segmentTwoX = segmentOneX + segmentWidth + 2; uint16_t segmentTwoY = segmentOneY + segmentHeight + 2; uint16_t segmentThreeX = segmentOneX; uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2; uint16_t segmentFourX = x; uint16_t segmentFourY = y + segmentHeight + 2; drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight); drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight); drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight); drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight); } } */ // Draw a digital clock void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->clear(); display->setTextAlignment(TEXT_ALIGN_LEFT); // === Set Title, Blank for Clock const char *titleStr = ""; // === Header === graphics::drawCommonHeader(display, x, y, titleStr, true); #ifdef T_WATCH_S3 if (nimbleBluetooth && nimbleBluetooth->isConnected()) { graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14); } #endif uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone char timeString[16]; int hour = 0; int minute = 0; int second = 0; if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; hour = hms / SEC_PER_HOUR; minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN; second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN } bool isPM = hour >= 12; // hour = hour > 12 ? hour - 12 : hour; if (config.display.use_12h_clock) { hour %= 12; if (hour == 0) hour = 12; snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute); } else { snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute); } // Format seconds string char secondString[8]; snprintf(secondString, sizeof(secondString), "%02d", second); #ifdef T_WATCH_S3 float scale = 1.5; #elif defined(CHATTER_2) float scale = 1.1; #else float scale = 0.75; if (isHighResolution) { scale = 1.5; } #endif uint16_t segmentWidth = SEGMENT_WIDTH * scale; uint16_t segmentHeight = SEGMENT_HEIGHT * scale; // calculate hours:minutes string width uint16_t timeStringWidth = strlen(timeString) * 5; for (uint8_t i = 0; i < strlen(timeString); i++) { char character = timeString[i]; if (character == ':') { timeStringWidth += segmentHeight; } else { timeStringWidth += segmentWidth + (segmentHeight * 2) + 4; } } uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2); uint16_t startingHourMinuteTextX = hourMinuteTextX; uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2); // iterate over characters in hours:minutes string and draw segmented characters for (uint8_t i = 0; i < strlen(timeString); i++) { char character = timeString[i]; if (character == ':') { drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale); hourMinuteTextX += segmentHeight + 6; } else { drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale); hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4; } hourMinuteTextX += 5; } // draw seconds string display->setFont(FONT_SMALL); int xOffset = (isHighResolution) ? 0 : -1; if (hour >= 10) { xOffset += (isHighResolution) ? 32 : 18; } int yOffset = (isHighResolution) ? 3 : 1; #ifdef SENSECAP_INDICATOR yOffset -= 3; #endif #ifdef T_DECK yOffset -= 5; #endif if (config.display.use_12h_clock) { display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2, isPM ? "pm" : "am"); } #ifndef USE_EINK xOffset = (isHighResolution) ? 18 : 10; display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset, secondString); #endif } void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y) { display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon); } // Draw an analog clock void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { display->setTextAlignment(TEXT_ALIGN_LEFT); // === Set Title, Blank for Clock const char *titleStr = ""; // === Header === graphics::drawCommonHeader(display, x, y, titleStr, true); #ifdef T_WATCH_S3 if (nimbleBluetooth && nimbleBluetooth->isConnected()) { drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14); } #endif // clock face center coordinates int16_t centerX = display->getWidth() / 2; int16_t centerY = display->getHeight() / 2; // clock face radius int16_t radius = 0; if (display->getHeight() < display->getWidth()) { radius = (display->getHeight() / 2) * 0.9; } else { radius = (display->getWidth() / 2) * 0.9; } #ifdef T_WATCH_S3 radius = (display->getWidth() / 2) * 0.8; #endif // noon (0 deg) coordinates (outermost circle) int16_t noonX = centerX; int16_t noonY = centerY - radius; // second hand radius and y coordinate (outermost circle) int16_t secondHandNoonY = noonY + 1; // tick mark outer y coordinate; (first nested circle) int16_t tickMarkOuterNoonY = secondHandNoonY; // seconds tick mark inner y coordinate; (second nested circle) double secondsTickMarkInnerNoonY = (double)noonY + 4; if (isHighResolution) { secondsTickMarkInnerNoonY = (double)noonY + 8; } // hours tick mark inner y coordinate; (third nested circle) double hoursTickMarkInnerNoonY = (double)noonY + 6; if (isHighResolution) { hoursTickMarkInnerNoonY = (double)noonY + 16; } // minute hand y coordinate int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4; // hour string y coordinate int16_t hourStringNoonY = minuteHandNoonY + 18; // hour hand radius and y coordinate int16_t hourHandRadius = radius * 0.35; if (isHighResolution) { hourHandRadius = radius * 0.55; } int16_t hourHandNoonY = centerY - hourHandRadius; display->setColor(OLEDDISPLAY_COLOR::WHITE); display->drawCircle(centerX, centerY, radius); uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone if (rtc_sec > 0) { long hms = rtc_sec % SEC_PER_DAY; hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; // Tear apart hms into h:m:s int hour = hms / SEC_PER_HOUR; int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN; int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN bool isPM = hour >= 12; if (config.display.use_12h_clock) { isPM = hour >= 12; display->setFont(FONT_SMALL); int yOffset = isHighResolution ? 1 : 0; #ifdef USE_EINK yOffset += 3; #endif display->drawString(centerX - (display->getStringWidth(isPM ? "pm" : "am") / 2), centerY + yOffset, isPM ? "pm" : "am"); } hour %= 12; if (hour == 0) hour = 12; int16_t degreesPerHour = 30; int16_t degreesPerMinuteOrSecond = 6; double hourBaseAngle = hour * degreesPerHour; double hourAngleOffset = ((double)minute / 60) * degreesPerHour; double hourAngle = radians(hourBaseAngle + hourAngleOffset); double minuteBaseAngle = minute * degreesPerMinuteOrSecond; double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond; double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset); double secondAngle = radians(second * degreesPerMinuteOrSecond); double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX; double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY; double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX; double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY; double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX; double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY; display->setFont(FONT_MEDIUM); // draw minute and hour tick marks and hour numbers for (uint16_t angle = 0; angle < 360; angle += 6) { double angleInRadians = radians(angle); double sineAngleInRadians = sin(-angleInRadians); double cosineAngleInRadians = cos(-angleInRadians); double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX; double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY; if (angle % degreesPerHour == 0) { double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX; double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY; // draw hour tick mark display->drawLine(startX, startY, endX, endY); static char buffer[2]; uint8_t hourInt = (angle / 30); if (hourInt == 0) { hourInt = 12; } // hour number x offset needs to be adjusted for some cases int8_t hourStringXOffset; int8_t hourStringYOffset = 13; switch (hourInt) { case 3: hourStringXOffset = 5; break; case 9: hourStringXOffset = 7; break; case 10: case 11: hourStringXOffset = 8; break; case 12: hourStringXOffset = 13; break; default: hourStringXOffset = 6; break; } double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset; double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset; #ifdef T_WATCH_S3 // draw hour number display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt); #else #ifdef USE_EINK if (isHighResolution) { // draw hour number display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt); } #else if (isHighResolution && (hourInt == 3 || hourInt == 6 || hourInt == 9 || hourInt == 12)) { // draw hour number display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt); } #endif #endif } if (angle % degreesPerMinuteOrSecond == 0) { double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX; double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY; if (isHighResolution) { // draw minute tick mark display->drawLine(startX, startY, endX, endY); } } } // draw hour hand display->drawLine(centerX, centerY, hourX, hourY); // draw minute hand display->drawLine(centerX, centerY, minuteX, minuteY); #ifndef USE_EINK // draw second hand display->drawLine(centerX, centerY, secondX, secondY); #endif } } } // namespace ClockRenderer } // namespace graphics #endif