mirror of
https://github.com/meshtastic/firmware.git
synced 2025-07-30 02:15:41 +00:00

* Clock updates and some TDeck corrections * TDeck Brightness Works in TFT Builds * Remove HAS_TFT from enabling Brightness, disable Brightness for TDeck * Add default textMessage frame actions and adjust SharedUIDisplay --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
521 lines
18 KiB
C++
521 lines
18 KiB
C++
#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;
|
|
bool isPM = 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) {
|
|
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
|
|
|
|
bool isPM = hour >= 12;
|
|
if (config.display.use_12h_clock) {
|
|
bool 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 |