Skip to content

Commit add77f9

Browse files
DavisNTmark9064
authored andcommitted
Add Legend watch face
An elegant and very practical digital watch face. Uses only fonts already in InfiniTime and no external resources. Has the standard functionality: * shows time, date (without year) and day of week * supports 24 and 12 hour time formats * BLE, new notification and alarm clock icons * battery percentage (green when charging) * steps and heart rate * temperature and weather icon Additionally: * visual indication (amber dot) for any notification(s) * visual indication (red or blue line) when notifications are muted * battery percentage turns red when 20% or less * stopwatch and timer icons (when active) * colorful sun, snow and thunderstorm weather icons * generally uses brighter colors for more urgent things
1 parent b02e59b commit add77f9

6 files changed

Lines changed: 479 additions & 0 deletions

File tree

‎src/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ list(APPEND SOURCE_FILES
431431
displayapp/screens/WatchFaceCasioStyleG7710.cpp
432432
displayapp/screens/WatchFaceNumerals.cpp
433433
displayapp/screens/WatchFacePrideFlag.cpp
434+
displayapp/screens/WatchFaceLegend.cpp
434435

435436
##
436437

‎src/displayapp/UserApps.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "displayapp/screens/WatchFaceTerminal.h"
1717
#include "displayapp/screens/WatchFaceNumerals.h"
1818
#include "displayapp/screens/WatchFacePrideFlag.h"
19+
#include "displayapp/screens/WatchFaceLegend.h"
1920

2021
namespace Pinetime {
2122
namespace Applications {

‎src/displayapp/apps/Apps.h.in‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ namespace Pinetime {
5757
CasioStyleG7710,
5858
Numerals,
5959
PrideFlag,
60+
Legend,
6061
};
6162

6263
template <Apps>

‎src/displayapp/apps/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ else()
3030
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710")
3131
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Numerals")
3232
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PrideFlag")
33+
set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Legend")
3334
set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware")
3435
endif()
3536

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
#include "displayapp/screens/WatchFaceLegend.h"
2+
3+
#include <lvgl/lvgl.h>
4+
#include "displayapp/screens/BleIcon.h"
5+
#include "displayapp/screens/NotificationIcon.h"
6+
#include "displayapp/screens/Symbols.h"
7+
#include "displayapp/screens/WeatherSymbols.h"
8+
#include "components/battery/BatteryController.h"
9+
#include "components/ble/BleController.h"
10+
#include "components/ble/NotificationManager.h"
11+
#include "components/heartrate/HeartRateController.h"
12+
#include "components/motion/MotionController.h"
13+
#include "components/settings/Settings.h"
14+
15+
using namespace Pinetime::Applications::Screens;
16+
17+
WatchFaceLegend::WatchFaceLegend(Controllers::DateTime& dateTimeController,
18+
const Controllers::Battery& batteryController,
19+
const Controllers::Ble& bleController,
20+
const Controllers::AlarmController& alarmController,
21+
Controllers::Timer& timerController,
22+
Controllers::StopWatchController& stopWatchController,
23+
Controllers::NotificationManager& notificationManager,
24+
Controllers::Settings& settingsController,
25+
Controllers::HeartRateController& heartRateController,
26+
Controllers::MotionController& motionController,
27+
Controllers::SimpleWeatherService& weatherService)
28+
: dateTimeController {dateTimeController},
29+
batteryController {batteryController},
30+
bleController {bleController},
31+
alarmController {alarmController},
32+
timerController {timerController},
33+
stopWatchController {stopWatchController},
34+
notificationManager {notificationManager},
35+
settingsController {settingsController},
36+
heartRateController {heartRateController},
37+
motionController {motionController},
38+
weatherService {weatherService} {
39+
40+
// ---- Top status strip -------------------------------------------------
41+
// Any notification indicator (top left). Hidden when notifications are empty.
42+
notificationAnyDot = lv_obj_create(lv_scr_act(), nullptr);
43+
lv_obj_set_size(notificationAnyDot, 7, 7);
44+
lv_obj_set_style_local_bg_color(notificationAnyDot, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, colorAnyNotification);
45+
lv_obj_set_style_local_radius(notificationAnyDot, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
46+
lv_obj_set_style_local_border_width(notificationAnyDot, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, 0);
47+
lv_obj_align(notificationAnyDot, nullptr, LV_ALIGN_IN_TOP_LEFT, 18, 16);
48+
lv_obj_set_hidden(notificationAnyDot, true); // hidden by default
49+
50+
// Standard new notification indicator (used when GetNotificationStatus() is Off or Sleep).
51+
notificationNewIcon = lv_label_create(lv_scr_act(), nullptr);
52+
lv_obj_set_style_local_text_color(notificationNewIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorRed);
53+
lv_label_set_text_static(notificationNewIcon, "");
54+
lv_obj_align(notificationNewIcon, notificationAnyDot, LV_ALIGN_OUT_RIGHT_MID, 10, 0);
55+
lv_obj_set_auto_realign(notificationNewIcon, true);
56+
57+
// Services icons. Empty string when nothing is active, can contain multiple icons.
58+
servicesIcons = lv_label_create(lv_scr_act(), nullptr);
59+
lv_obj_set_style_local_text_color(servicesIcons, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
60+
lv_label_set_text_static(servicesIcons, "");
61+
lv_obj_align(servicesIcons, notificationNewIcon, LV_ALIGN_OUT_RIGHT_MID, 12, 1);
62+
lv_obj_set_auto_realign(servicesIcons, true);
63+
64+
// Numeric battery (top right).
65+
batteryValue = lv_label_create(lv_scr_act(), nullptr);
66+
lv_obj_set_style_local_text_color(batteryValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
67+
lv_label_set_text_static(batteryValue, "--%");
68+
lv_obj_align(batteryValue, nullptr, LV_ALIGN_IN_TOP_RIGHT, -5, 10);
69+
lv_obj_set_auto_realign(batteryValue, true);
70+
71+
// BLE icon, left of the battery percentage.
72+
bleIcon = lv_label_create(lv_scr_act(), nullptr);
73+
lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorBlue);
74+
lv_label_set_text_static(bleIcon, "");
75+
lv_obj_align(bleIcon, batteryValue, LV_ALIGN_OUT_LEFT_MID, -10, 0);
76+
lv_obj_set_auto_realign(bleIcon, true);
77+
78+
// Upper divider - displayed (red or blue) when GetNotificationStatus() is Off or Sleep
79+
upperDivider = lv_line_create(lv_scr_act(), nullptr);
80+
lv_line_set_points(upperDivider, dividerPoints, 2);
81+
lv_obj_set_style_local_line_width(upperDivider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 1);
82+
lv_obj_align(upperDivider, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 45);
83+
lv_obj_set_hidden(upperDivider, true); // hidden by default
84+
85+
// ---- Center block ------------------------------------------------------
86+
labelDate = lv_label_create(lv_scr_act(), nullptr);
87+
lv_obj_set_style_local_text_color(labelDate, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
88+
lv_label_set_text_static(labelDate, "");
89+
lv_obj_align(labelDate, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 64);
90+
91+
labelTime = lv_label_create(lv_scr_act(), nullptr);
92+
lv_obj_set_style_local_text_font(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
93+
lv_obj_set_style_local_text_color(labelTime, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorForeground);
94+
lv_label_set_text_static(labelTime, "");
95+
lv_obj_align(labelTime, nullptr, LV_ALIGN_IN_TOP_LEFT, 0, 94);
96+
97+
labelAmPm = lv_label_create(lv_scr_act(), nullptr);
98+
lv_obj_set_style_local_text_color(labelAmPm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorForeground);
99+
lv_label_set_text_static(labelAmPm, "");
100+
lv_obj_align(labelAmPm, nullptr, LV_ALIGN_IN_TOP_RIGHT, -4, 64);
101+
lv_obj_set_auto_realign(labelAmPm, true);
102+
103+
divider = lv_line_create(lv_scr_act(), nullptr);
104+
lv_line_set_points(divider, dividerPoints, 2);
105+
lv_obj_set_style_local_line_color(divider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, colorDivider);
106+
lv_obj_set_style_local_line_width(divider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 1);
107+
lv_obj_align(divider, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 177);
108+
109+
// ---- Bottom row --------------------------------------------------
110+
stepValue = lv_label_create(lv_scr_act(), nullptr);
111+
lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
112+
lv_label_set_text_static(stepValue, "0");
113+
lv_obj_align(stepValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 186);
114+
115+
stepCaption = lv_label_create(lv_scr_act(), nullptr);
116+
lv_obj_set_style_local_text_color(stepCaption, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorCaption);
117+
lv_label_set_text_static(stepCaption, "steps");
118+
lv_obj_align(stepCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 4, 210);
119+
120+
heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
121+
lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
122+
lv_label_set_text_static(heartbeatValue, "--");
123+
lv_obj_align(heartbeatValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 100, 186);
124+
125+
heartbeatCaption = lv_label_create(lv_scr_act(), nullptr);
126+
lv_obj_set_style_local_text_color(heartbeatCaption, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorCaption);
127+
lv_label_set_text_static(heartbeatCaption, "BPM");
128+
lv_obj_align(heartbeatCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 100, 210);
129+
130+
weatherValue = lv_label_create(lv_scr_act(), nullptr);
131+
lv_obj_set_style_local_text_color(weatherValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
132+
lv_label_set_text_static(weatherValue, "--");
133+
lv_obj_align(weatherValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 186);
134+
135+
weatherCaption = lv_label_create(lv_scr_act(), nullptr);
136+
lv_obj_set_style_local_text_color(weatherCaption, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorCaption);
137+
lv_label_set_text_static(weatherCaption, "\xC2\xB0");
138+
lv_obj_align(weatherCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 210);
139+
140+
weatherIcon = lv_label_create(lv_scr_act(), nullptr);
141+
lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, colorSecondary);
142+
lv_obj_set_style_local_text_font(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &fontawesome_weathericons);
143+
lv_label_set_text_static(weatherIcon, "");
144+
lv_obj_align(weatherIcon, weatherValue, LV_ALIGN_OUT_LEFT_BOTTOM, -5, 12);
145+
lv_obj_set_auto_realign(weatherIcon, true);
146+
147+
Refresh();
148+
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
149+
}
150+
151+
WatchFaceLegend::~WatchFaceLegend() {
152+
lv_task_del(taskRefresh);
153+
lv_obj_clean(lv_scr_act());
154+
}
155+
156+
void WatchFaceLegend::Refresh() {
157+
// ---- Notification indicators ----
158+
notificationState = notificationManager.AreNewNotificationsAvailable();
159+
notificationEmpty = notificationManager.IsEmpty();
160+
if (notificationState.IsUpdated() || notificationEmpty.IsUpdated()) {
161+
bool state = notificationState.Get();
162+
bool empty = notificationEmpty.Get();
163+
lv_obj_set_hidden(notificationAnyDot, empty && !state);
164+
lv_label_set_text_static(notificationNewIcon, NotificationIcon::GetIcon(state));
165+
lv_obj_align(servicesIcons, state ? notificationNewIcon : notificationAnyDot, LV_ALIGN_OUT_RIGHT_MID, empty && !state ? -18 : 12, 1);
166+
}
167+
168+
// ---- Services (Alarm, Timer, StopWatch) ----
169+
alarmEnabled = alarmController.IsEnabled();
170+
timerRunning = timerController.IsRunning();
171+
stopWatchRunning = stopWatchController.IsRunning();
172+
if (alarmEnabled.IsUpdated() || timerRunning.IsUpdated() || stopWatchRunning.IsUpdated()) {
173+
lv_label_set_text_fmt(servicesIcons,
174+
"%s%s%s%s%s",
175+
stopWatchRunning.Get() ? Symbols::stopWatch : "",
176+
stopWatchRunning.Get() ? " " : "",
177+
alarmEnabled.Get() ? Symbols::bell : "",
178+
alarmEnabled.Get() ? " " : "",
179+
timerRunning.Get() ? Symbols::hourGlass : "");
180+
}
181+
182+
// ---- BLE ----
183+
bleState = bleController.IsConnected();
184+
bleRadioEnabled = bleController.IsRadioEnabled();
185+
if (bleState.IsUpdated() || bleRadioEnabled.IsUpdated()) {
186+
lv_label_set_text_static(bleIcon, BleIcon::GetIcon(bleState.Get()));
187+
}
188+
189+
// ---- Battery ----
190+
batteryPercentRemaining = batteryController.PercentRemaining();
191+
powerPresent = batteryController.IsPowerPresent();
192+
if (batteryPercentRemaining.IsUpdated() || powerPresent.IsUpdated()) {
193+
uint8_t remaining = batteryPercentRemaining.Get();
194+
lv_label_set_text_fmt(batteryValue, "%d%%", remaining);
195+
lv_obj_set_style_local_text_color(batteryValue,
196+
LV_LABEL_PART_MAIN,
197+
LV_STATE_DEFAULT,
198+
powerPresent.Get() ? colorCharging
199+
: remaining > 20 ? colorSecondary
200+
: colorRed);
201+
lv_obj_align(bleIcon, batteryValue, LV_ALIGN_OUT_LEFT_MID, -10, 0);
202+
}
203+
204+
// ---- Upper divider, based on notification config ----
205+
notificationConfig = settingsController.GetNotificationStatus();
206+
if (notificationConfig.IsUpdated()) {
207+
switch (notificationConfig.Get()) {
208+
case Controllers::Settings::Notification::Off:
209+
lv_obj_set_style_local_line_color(upperDivider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, colorRed);
210+
lv_obj_set_hidden(upperDivider, false);
211+
break;
212+
case Controllers::Settings::Notification::Sleep:
213+
lv_obj_set_style_local_line_color(upperDivider, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, colorBlue);
214+
lv_obj_set_hidden(upperDivider, false);
215+
break;
216+
default:
217+
lv_obj_set_hidden(upperDivider, true);
218+
break;
219+
}
220+
}
221+
222+
// ---- Time and date ----
223+
currentDateTime = std::chrono::time_point_cast<std::chrono::minutes>(dateTimeController.CurrentDateTime());
224+
if (currentDateTime.IsUpdated()) {
225+
uint8_t hour = dateTimeController.Hours();
226+
uint8_t minute = dateTimeController.Minutes();
227+
228+
if (settingsController.GetClockType() == Controllers::Settings::ClockType::H12) {
229+
const char* ampm = "AM";
230+
if (hour == 0) {
231+
hour = 12;
232+
} else if (hour == 12) {
233+
ampm = "PM";
234+
} else if (hour > 12) {
235+
hour = hour - 12;
236+
ampm = "PM";
237+
}
238+
lv_label_set_text_static(labelAmPm, ampm);
239+
} else {
240+
lv_label_set_text_static(labelAmPm, "");
241+
}
242+
lv_label_set_text_fmt(labelTime, "%02d:%02d", hour, minute);
243+
244+
currentDate = std::chrono::time_point_cast<std::chrono::days>(currentDateTime.Get());
245+
if (currentDate.IsUpdated()) {
246+
lv_label_set_text_fmt(labelDate,
247+
"%s %02d %s",
248+
dateTimeController.DayOfWeekShortToString(),
249+
dateTimeController.Day(),
250+
dateTimeController.MonthShortToString());
251+
}
252+
}
253+
254+
// ---- Steps ----
255+
stepCount = motionController.NbSteps();
256+
if (stepCount.IsUpdated()) {
257+
lv_label_set_text_fmt(stepValue, "%lu", stepCount.Get());
258+
}
259+
260+
// ---- Heart rate ----
261+
heartbeat = heartRateController.HeartRate();
262+
heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
263+
if (heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
264+
if (heartbeatRunning.Get()) {
265+
lv_label_set_text_fmt(heartbeatValue, "%d", heartbeat.Get());
266+
} else {
267+
lv_label_set_text_static(heartbeatValue, "--");
268+
}
269+
}
270+
271+
// ---- Weather ----
272+
currentWeather = weatherService.Current();
273+
if (currentWeather.IsUpdated()) {
274+
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
275+
lv_label_set_text_static(weatherCaption,
276+
"\xC2\xB0"
277+
"F");
278+
} else {
279+
lv_label_set_text_static(weatherCaption,
280+
"\xC2\xB0"
281+
"C");
282+
}
283+
284+
auto optCurrentWeather = currentWeather.Get();
285+
if (optCurrentWeather) {
286+
int16_t temp;
287+
if (settingsController.GetWeatherFormat() == Controllers::Settings::WeatherFormat::Imperial) {
288+
temp = optCurrentWeather->temperature.Fahrenheit();
289+
} else {
290+
temp = optCurrentWeather->temperature.Celsius();
291+
}
292+
if (temp > -100) {
293+
lv_label_set_text_fmt(weatherValue, "%d", temp);
294+
} else {
295+
lv_label_set_text_static(weatherValue, "--");
296+
}
297+
298+
lv_color_t color;
299+
switch (optCurrentWeather->iconId) {
300+
case Controllers::SimpleWeatherService::Icons::Sun:
301+
color = colorWeatherYellow;
302+
break;
303+
case Controllers::SimpleWeatherService::Icons::Snow:
304+
case Controllers::SimpleWeatherService::Icons::Thunderstorm:
305+
color = colorWeatherLightBlue;
306+
break;
307+
default:
308+
color = colorWeatherDefault;
309+
break;
310+
}
311+
lv_obj_set_style_local_text_color(weatherIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color);
312+
lv_label_set_text_static(weatherIcon, Symbols::GetSymbol(optCurrentWeather->iconId, weatherService.IsNight()));
313+
314+
if (!weatherDisplayed) { // adjust the layout when necessary
315+
lv_obj_align(weatherValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 200, 186);
316+
lv_obj_align(weatherCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 200, 210);
317+
lv_obj_align(weatherIcon, weatherValue, LV_ALIGN_OUT_LEFT_BOTTOM, -5, 12);
318+
weatherDisplayed = true;
319+
}
320+
} else {
321+
lv_label_set_text_static(weatherValue, "--");
322+
lv_label_set_text_static(weatherIcon, "");
323+
324+
if (weatherDisplayed) { // adjust the layout when necessary
325+
lv_obj_align(weatherValue, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 186);
326+
lv_obj_align(weatherCaption, nullptr, LV_ALIGN_IN_TOP_LEFT, 188, 210);
327+
lv_obj_align(weatherIcon, weatherValue, LV_ALIGN_OUT_LEFT_BOTTOM, -5, 12);
328+
weatherDisplayed = false;
329+
}
330+
}
331+
}
332+
}

0 commit comments

Comments
 (0)