diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 91aa82f23..049771e88 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -28,6 +28,7 @@ along with this program. If not, see .
#include
#include "DisplayFormatters.h"
+#include "draw/ClockRenderer.h"
#include "draw/DebugRenderer.h"
#include "draw/MessageRenderer.h"
#include "draw/NodeListRenderer.h"
@@ -97,9 +98,6 @@ static uint32_t targetFramerate = IDLE_FRAMERATE;
uint32_t logo_timeout = 5000; // 4 seconds for EACH logo
-// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function
-uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
-
// Threshold values for the GPS lock accuracy bar display
uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100};
@@ -174,443 +172,6 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
return packet->from != 0 && !moduleConfig.store_forward.enabled;
}
-#if defined(DISPLAY_CLOCK_FRAME)
-
-void Screen::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 Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
-{
- display->setTextAlignment(TEXT_ALIGN_LEFT);
-
- UIRenderer::drawBattery(display, x, y + 7, imgBattery, powerStatus);
-
- if (powerStatus->getHasBattery()) {
- char batteryPercent[8];
- snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent());
-
- display->setFont(FONT_SMALL);
-
- display->drawString(x + 20, y + 2, batteryPercent);
- }
-
- if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
- drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
- }
-
- drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
-
- display->setColor(OLEDDISPLAY_COLOR::WHITE);
-
- 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;
-
- 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
-
- hour = hour > 12 ? hour - 12 : hour;
-
- if (hour == 0) {
- hour = 12;
- }
-
- // Format time string
- char timeString[16];
- snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
-
- // Format seconds string
- char secondString[8];
- snprintf(secondString, sizeof(secondString), "%02d", second);
-
- float scale = 1.5;
-
- 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;
- }
- }
-
- // calculate seconds string width
- uint16_t secondStringWidth = (strlen(secondString) * 12) + 4;
-
- // sum these to get total string width
- uint16_t totalWidth = timeStringWidth + secondStringWidth;
-
- uint16_t hourMinuteTextX = (display->getWidth() / 2) - (totalWidth / 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_MEDIUM);
- display->drawString(startingHourMinuteTextX + timeStringWidth + 4,
- (display->getHeight() - hourMinuteTextY) - FONT_HEIGHT_MEDIUM + 6, secondString);
- }
-}
-
-void Screen::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 Screen::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]) {
- drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
- }
-
- if (numbers[number][1]) {
- drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
- }
-
- if (numbers[number][2]) {
- drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
- }
-
- if (numbers[number][3]) {
- drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
- }
-
- if (numbers[number][4]) {
- drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
- }
-
- if (numbers[number][5]) {
- drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
- }
-
- if (numbers[number][6]) {
- drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
- }
-}
-
-void Screen::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 Screen::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 Screen::drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
-{
- display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
-}
-
-// Draw an analog clock
-void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
-{
- display->setTextAlignment(TEXT_ALIGN_LEFT);
-
- UIRenderer::drawBattery(display, x, y + 7, imgBattery, powerStatus);
-
- if (powerStatus->getHasBattery()) {
- char batteryPercent[8];
- snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent());
-
- display->setFont(FONT_SMALL);
-
- display->drawString(x + 20, y + 2, batteryPercent);
- }
-
- if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
- drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
- }
-
- drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
-
- // clock face center coordinates
- int16_t centerX = display->getWidth() / 2;
- int16_t centerY = display->getHeight() / 2;
-
- // clock face radius
- int16_t radius = (display->getWidth() / 2) * 0.8;
-
- // 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 + 8;
-
- // hours tick mark inner y coordinate; (third nested circle)
- double 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.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
-
- hour = hour > 12 ? hour - 12 : hour;
-
- 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;
-
- // draw hour number
- display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
- }
-
- if (angle % degreesPerMinuteOrSecond == 0) {
- double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
- double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
-
- // 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);
-
- // draw second hand
- display->drawLine(centerX, centerY, secondX, secondY);
- }
-}
-
-#endif
-
// Get an absolute time from "seconds ago" info. Returns false if no valid timestamp possible
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo)
{
@@ -1373,7 +934,8 @@ void Screen::setFrames(FrameFocus focus)
}
#if defined(DISPLAY_CLOCK_FRAME)
- normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame;
+ normalFrames[numframes++] = graphics::ClockRenderer::digitalWatchFace ? graphics::ClockRenderer::drawDigitalClockFrame
+ : &graphics::ClockRenderer::drawAnalogClockFrame;
indicatorIcons.push_back(icon_clock);
#endif
@@ -1422,6 +984,10 @@ void Screen::setFrames(FrameFocus focus)
normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage;
indicatorIcons.push_back(icon_memory);
}
+#if !defined(DISPLAY_CLOCK_FRAME)
+ normalFrames[numframes++] = graphics::ClockRenderer::drawDigitalClockFrame;
+ indicatorIcons.push_back(icon_clock);
+#endif
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
@@ -1431,17 +997,6 @@ void Screen::setFrames(FrameFocus focus)
}
}
- // then the debug info
-
- // Since frames are basic function pointers, we have to use a helper to
- // call a method on debugInfo object.
- // fsi.positions.log = numframes;
- // normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoTrampoline;
-
- // call a method on debugInfoScreen object (for more details)
- // fsi.positions.settings = numframes;
- // normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoSettingsTrampoline;
-
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!dismissedFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes;
@@ -1768,19 +1323,21 @@ int Screen::handleInputEvent(const InputEvent *event)
ui->update();
return 0;
}
-#if defined(DISPLAY_CLOCK_FRAME)
- // For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button
- uint8_t watchFaceFrame = error_code ? 1 : 0;
+ /*
+ #if defined(DISPLAY_CLOCK_FRAME)
+ // For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button
+ uint8_t watchFaceFrame = error_code ? 1 : 0;
- if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 &&
- event->touchY >= 204 && event->touchY <= 240) {
- screen->digitalWatchFace = !screen->digitalWatchFace;
+ if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 &&
+ event->touchY >= 204 && event->touchY <= 240) {
+ screen->digitalWatchFace = !screen->digitalWatchFace;
- setFrames();
+ setFrames();
- return 0;
- }
-#endif
+ return 0;
+ }
+ #endif
+ */
// Use left or right input from a keyboard to move between frames,
// so long as a mesh module isn't using these events for some other purpose
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index fff52da77..185462028 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -636,27 +636,6 @@ class Screen : public concurrency::OSThread
// Sets frame up for immediate drawing
void setFrameImmediateDraw(FrameCallback *drawFrames);
-#if defined(DISPLAY_CLOCK_FRAME)
- static void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
-
- static void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
-
- static void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
-
- static void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
-
- static void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
-
- static void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
-
- static void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
-
- static void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
-
- // Whether we are showing the digital watch face or the analog one
- bool digitalWatchFace = true;
-#endif
-
/// callback for current alert frame
FrameCallback alertFrame;
diff --git a/src/graphics/draw/ClockRenderer.cpp b/src/graphics/draw/ClockRenderer.cpp
new file mode 100644
index 000000000..7700b0731
--- /dev/null
+++ b/src/graphics/draw/ClockRenderer.cpp
@@ -0,0 +1,462 @@
+#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
+
+#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);
+ int line = 1;
+
+#ifdef T_WATCH_S3
+ if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
+ graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
+ }
+
+ drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36,
+ graphics::ClockRenderer::digitalWatchFace, 1);
+#endif
+
+ 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;
+
+ 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
+
+ hour = hour > 12 ? hour - 12 : hour;
+
+ if (hour == 0) {
+ hour = 12;
+ }
+
+ // Format time string
+ char timeString[16];
+ snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
+
+ // Format seconds string
+ char secondString[8];
+ snprintf(secondString, sizeof(secondString), "%02d", second);
+
+#ifdef T_WATCH_S3
+ float scale = 1.5;
+#else
+ float scale = 0.75;
+ if (SCREEN_WIDTH > 128) {
+ 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;
+ }
+ }
+
+ // calculate seconds string width
+ uint16_t secondStringWidth = (strlen(secondString) * 12) + 4;
+
+ // sum these to get total string width
+ uint16_t totalWidth = timeStringWidth + secondStringWidth;
+
+ uint16_t hourMinuteTextX = (display->getWidth() / 2) - (totalWidth / 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_MEDIUM);
+ display->drawString(startingHourMinuteTextX + timeStringWidth + 4,
+ (display->getHeight() - hourMinuteTextY) - FONT_HEIGHT_MEDIUM + 6, secondString);
+ }
+}
+
+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);
+
+ graphics::UIRenderer::drawBattery(display, x, y + 7, imgBattery, powerStatus);
+
+ if (powerStatus->getHasBattery()) {
+ char batteryPercent[8];
+ snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent());
+
+ display->setFont(FONT_SMALL);
+
+ display->drawString(x + 20, y + 2, batteryPercent);
+ }
+
+ if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
+ drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
+ }
+
+ drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36,
+ graphics::ClockRenderer::digitalWatchFace, 1);
+
+ // clock face center coordinates
+ int16_t centerX = display->getWidth() / 2;
+ int16_t centerY = display->getHeight() / 2;
+
+ // clock face radius
+ int16_t radius = (display->getWidth() / 2) * 0.8;
+
+ // 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 + 8;
+
+ // hours tick mark inner y coordinate; (third nested circle)
+ double 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.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
+
+ hour = hour > 12 ? hour - 12 : hour;
+
+ 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;
+
+ // draw hour number
+ display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
+ }
+
+ if (angle % degreesPerMinuteOrSecond == 0) {
+ double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
+ double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
+
+ // 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);
+
+ // draw second hand
+ display->drawLine(centerX, centerY, secondX, secondY);
+ }
+}
+
+} // namespace ClockRenderer
+
+} // namespace graphics
\ No newline at end of file
diff --git a/src/graphics/draw/ClockRenderer.h b/src/graphics/draw/ClockRenderer.h
index 23ad79d39..b679b6796 100644
--- a/src/graphics/draw/ClockRenderer.h
+++ b/src/graphics/draw/ClockRenderer.h
@@ -1,6 +1,5 @@
#pragma once
-#include "graphics/Screen.h"
#include
#include
@@ -10,14 +9,11 @@ namespace graphics
/// Forward declarations
class Screen;
-/**
- * @brief Clock drawing functions
- *
- * Contains all functions related to drawing analog and digital clocks,
- * segmented displays, and time-related UI elements.
- */
namespace ClockRenderer
{
+// Whether we are showing the digital watch face or the analog one
+static bool digitalWatchFace = true;
+
// Clock frame functions
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp
index 4b0c0b273..4b9effb4c 100644
--- a/src/graphics/draw/DebugRenderer.cpp
+++ b/src/graphics/draw/DebugRenderer.cpp
@@ -36,9 +36,6 @@ const int textPositions[7] = {textZeroLine, textFirstLine, textSecondLine, tex
using namespace meshtastic;
-// Battery icon array
-static uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
-
// External variables
extern graphics::Screen *screen;
extern PowerStatus *powerStatus;
diff --git a/src/graphics/images.h b/src/graphics/images.h
index 980052cd4..78e6cdb81 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -19,11 +19,12 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3
const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF};
const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF};
-#if defined(DISPLAY_CLOCK_FRAME)
const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0xe3, 0x1f,
0xf3, 0x3f, 0x33, 0x30, 0x33, 0x33, 0x33, 0x33, 0x03, 0x33, 0xff, 0x33,
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
-#endif
+
+// This image definition is here instead of images.h because it's modified dynamically by the drawBattery function
+static uint8_t imgBattery[16] = {0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xE7, 0x3C};
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \
diff --git a/src/input/ButtonThreadImpl.cpp b/src/input/ButtonThreadImpl.cpp
index c57f1c8f0..2c92d8163 100644
--- a/src/input/ButtonThreadImpl.cpp
+++ b/src/input/ButtonThreadImpl.cpp
@@ -2,8 +2,6 @@
#include "InputBroker.h"
#include "configuration.h"
-#if defined(BUTTON_PIN)
-
ButtonThreadImpl *aButtonThreadImpl;
ButtonThreadImpl::ButtonThreadImpl() : ButtonThread("UserButton") {}
@@ -13,5 +11,3 @@ void ButtonThreadImpl::init() // init should give the pin number and the action
if (inputBroker)
inputBroker->registerSource(this);
}
-
-#endif // INPUTBROKER_SERIAL_TYPE
\ No newline at end of file