Skip to content

Commit c575754

Browse files
authored
add basic metronome app (#409)
* add basic metronome app * add bpb, tap to bpm, update widgets * use event pressed for bpm tap * move case statement break to the right place * narrow bpm selection range, override touch events * fix arc knob style * re-enable sleeping in destructor
1 parent 58a8507 commit c575754

7 files changed

Lines changed: 226 additions & 3 deletions

File tree

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ list(APPEND SOURCE_FILES
396396
displayapp/screens/FirmwareUpdate.cpp
397397
displayapp/screens/Music.cpp
398398
displayapp/screens/Navigation.cpp
399+
displayapp/screens/Metronome.cpp
399400
displayapp/screens/Motion.cpp
400401
displayapp/screens/FirmwareValidation.cpp
401402
displayapp/screens/ApplicationList.cpp
@@ -592,6 +593,7 @@ set(INCLUDE_FILES
592593
displayapp/Apps.h
593594
displayapp/screens/Notifications.h
594595
displayapp/screens/HeartRate.h
596+
displayapp/screens/Metronome.h
595597
displayapp/screens/Motion.h
596598
displayapp/screens/Timer.h
597599
drivers/St7789.h

src/displayapp/Apps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ namespace Pinetime {
2121
HeartRate,
2222
Navigation,
2323
StopWatch,
24+
Metronome,
2425
Motion,
2526
Steps,
2627
QuickSettings,

src/displayapp/DisplayApp.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "displayapp/screens/Paddle.h"
1919
#include "displayapp/screens/StopWatch.h"
2020
#include "displayapp/screens/Meter.h"
21+
#include "displayapp/screens/Metronome.h"
2122
#include "displayapp/screens/Music.h"
2223
#include "displayapp/screens/Navigation.h"
2324
#include "displayapp/screens/Notifications.h"
@@ -318,7 +319,7 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
318319
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
319320
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
320321
break;
321-
case Apps::SettingSteps:
322+
case Apps::SettingSteps:
322323
currentScreen = std::make_unique<Screens::SettingSteps>(this, settingsController);
323324
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
324325
break;
@@ -356,10 +357,13 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
356357
case Apps::HeartRate:
357358
currentScreen = std::make_unique<Screens::HeartRate>(this, heartRateController, *systemTask);
358359
break;
360+
case Apps::Metronome:
361+
currentScreen = std::make_unique<Screens::Metronome>(this, motorController, systemTask);
362+
break;
359363
case Apps::Motion:
360364
currentScreen = std::make_unique<Screens::Motion>(this, motionController);
361365
break;
362-
case Apps::Steps:
366+
case Apps::Steps:
363367
currentScreen = std::make_unique<Screens::Steps>(this, motionController, settingsController);
364368
break;
365369
}

src/displayapp/lv_pinetime_theme.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ static lv_style_t style_sw_bg;
4848
static lv_style_t style_sw_indic;
4949
static lv_style_t style_sw_knob;
5050
static lv_style_t style_arc_bg;
51+
static lv_style_t style_arc_knob;
5152
static lv_style_t style_arc_indic;
5253
static lv_style_t style_table_cell;
5354
static lv_style_t style_pad_small;
@@ -191,6 +192,7 @@ static void basic_init(void) {
191192
lv_style_set_text_line_space(&style_ddlist_list, LV_STATE_DEFAULT, LV_VER_RES / 25);
192193
lv_style_set_shadow_width(&style_ddlist_list, LV_STATE_DEFAULT, LV_VER_RES / 20);
193194
lv_style_set_shadow_color(&style_ddlist_list, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
195+
lv_style_set_bg_color(&style_ddlist_list, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
194196

195197
style_init_reset(&style_ddlist_selected);
196198
lv_style_set_bg_opa(&style_ddlist_selected, LV_STATE_DEFAULT, LV_OPA_COVER);
@@ -239,6 +241,13 @@ static void basic_init(void) {
239241
lv_style_set_line_color(&style_arc_bg, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
240242
lv_style_set_line_width(&style_arc_bg, LV_STATE_DEFAULT, LV_DPX(25));
241243
lv_style_set_line_rounded(&style_arc_bg, LV_STATE_DEFAULT, true);
244+
lv_style_set_pad_all(&style_arc_bg, LV_STATE_DEFAULT, LV_DPX(5));
245+
246+
lv_style_reset(&style_arc_knob);
247+
lv_style_set_radius(&style_arc_knob, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
248+
lv_style_set_bg_opa(&style_arc_knob, LV_STATE_DEFAULT, LV_OPA_COVER);
249+
lv_style_set_bg_color(&style_arc_knob, LV_STATE_DEFAULT, LV_PINETIME_LIGHT_GRAY);
250+
lv_style_set_pad_all(&style_arc_knob, LV_STATE_DEFAULT, LV_DPX(5));
242251

243252
style_init_reset(&style_table_cell);
244253
lv_style_set_border_color(&style_table_cell, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
@@ -447,6 +456,10 @@ static void theme_apply(lv_obj_t* obj, lv_theme_style_t name) {
447456
lv_obj_clean_style_list(obj, LV_ARC_PART_INDIC);
448457
list = lv_obj_get_style_list(obj, LV_ARC_PART_INDIC);
449458
_lv_style_list_add_style(list, &style_arc_indic);
459+
460+
lv_obj_clean_style_list(obj, LV_ARC_PART_KNOB);
461+
list = lv_obj_get_style_list(obj, LV_ARC_PART_KNOB);
462+
_lv_style_list_add_style(list, &style_arc_knob);
450463
break;
451464

452465
case LV_THEME_SWITCH:

src/displayapp/screens/ApplicationList.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen2() {
6363
{Symbols::paddle, Apps::Paddle},
6464
{"2", Apps::Twos},
6565
{"M", Apps::Motion},
66-
{"", Apps::None},
66+
{"b", Apps::Metronome},
6767
{"", Apps::None},
6868
}};
6969

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#include "Metronome.h"
2+
3+
#include "Screen.h"
4+
#include "Symbols.h"
5+
#include "lvgl/lvgl.h"
6+
#include "FreeRTOSConfig.h"
7+
#include "task.h"
8+
9+
#include <string>
10+
#include <tuple>
11+
12+
using namespace Pinetime::Applications::Screens;
13+
14+
namespace {
15+
float calculateDelta(const TickType_t startTime, const TickType_t currentTime) {
16+
TickType_t delta = 0;
17+
// Take care of overflow
18+
if (startTime > currentTime) {
19+
delta = 0xffffffff - startTime;
20+
delta += (currentTime + 1);
21+
} else {
22+
delta = currentTime - startTime;
23+
}
24+
return static_cast<float>(delta) / static_cast<float>(configTICK_RATE_HZ);
25+
}
26+
27+
static void eventHandler(lv_obj_t* obj, lv_event_t event) {
28+
Metronome* screen = static_cast<Metronome*>(obj->user_data);
29+
screen->OnEvent(obj, event);
30+
}
31+
32+
lv_obj_t* createLabel(const char* name, lv_obj_t* reference, lv_align_t align, lv_font_t* font, uint8_t x = 0, uint8_t y = 0) {
33+
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
34+
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
35+
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
36+
lv_label_set_text(label, name);
37+
lv_obj_align(label, reference, align, x, y);
38+
39+
return label;
40+
}
41+
}
42+
43+
Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask)
44+
: Screen(app), running {true}, currentState {States::Stopped}, startTime {}, motorController {motorController}, systemTask {systemTask} {
45+
46+
bpmArc = lv_arc_create(lv_scr_act(), nullptr);
47+
bpmArc->user_data = this;
48+
lv_obj_set_event_cb(bpmArc, eventHandler);
49+
lv_arc_set_bg_angles(bpmArc, 0, 270);
50+
lv_arc_set_rotation(bpmArc, 135);
51+
lv_arc_set_range(bpmArc, 40, 220);
52+
lv_arc_set_value(bpmArc, bpm);
53+
lv_obj_set_size(bpmArc, 210, 210);
54+
lv_arc_set_adjustable(bpmArc, true);
55+
lv_obj_align(bpmArc, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 7);
56+
57+
bpmValue = createLabel(std::to_string(lv_arc_get_value(bpmArc)).c_str(), bpmArc, LV_ALIGN_IN_TOP_MID, &jetbrains_mono_76, 0, 55);
58+
bpmLegend = createLabel("bpm", bpmValue, LV_ALIGN_OUT_BOTTOM_MID, &jetbrains_mono_bold_20, 0, 0);
59+
60+
bpmTap = lv_btn_create(lv_scr_act(), nullptr);
61+
bpmTap->user_data = this;
62+
lv_obj_set_event_cb(bpmTap, eventHandler);
63+
lv_obj_set_style_local_bg_opa(bpmTap, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
64+
lv_obj_set_height(bpmTap, 80);
65+
lv_obj_align(bpmTap, bpmValue, LV_ALIGN_IN_TOP_MID, 0, 0);
66+
67+
bpbDropdown = lv_dropdown_create(lv_scr_act(), nullptr);
68+
bpbDropdown->user_data = this;
69+
lv_obj_set_event_cb(bpbDropdown, eventHandler);
70+
lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_MAIN, LV_STATE_DEFAULT, 20);
71+
lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_LIST, LV_STATE_DEFAULT, 20);
72+
lv_obj_align(bpbDropdown, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15, -4);
73+
lv_dropdown_set_options(bpbDropdown, "1\n2\n3\n4\n5\n6\n7\n8\n9");
74+
lv_dropdown_set_selected(bpbDropdown, bpb - 1);
75+
bpbLegend = lv_label_create(bpbDropdown, nullptr);
76+
lv_label_set_text(bpbLegend, "bpb");
77+
lv_obj_align(bpbLegend, bpbDropdown, LV_ALIGN_IN_RIGHT_MID, -15, 0);
78+
79+
playPause = lv_btn_create(lv_scr_act(), nullptr);
80+
playPause->user_data = this;
81+
lv_obj_set_event_cb(playPause, eventHandler);
82+
lv_obj_align(playPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -10);
83+
lv_obj_set_height(playPause, 39);
84+
playPauseLabel = lv_label_create(playPause, nullptr);
85+
lv_label_set_text(playPauseLabel, Symbols::play);
86+
87+
app->SetTouchMode(DisplayApp::TouchModes::Polling);
88+
}
89+
90+
Metronome::~Metronome() {
91+
app->SetTouchMode(DisplayApp::TouchModes::Gestures);
92+
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::EnableSleeping);
93+
lv_obj_clean(lv_scr_act());
94+
}
95+
96+
bool Metronome::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
97+
return true;
98+
}
99+
100+
bool Metronome::Refresh() {
101+
switch (currentState) {
102+
case States::Stopped: {
103+
break;
104+
}
105+
case States::Running: {
106+
if (calculateDelta(startTime, xTaskGetTickCount()) >= (60.0 / bpm)) {
107+
counter--;
108+
startTime -= 60.0 / bpm;
109+
startTime = xTaskGetTickCount();
110+
if (counter == 0) {
111+
counter = bpb;
112+
motorController.SetDuration(90);
113+
} else {
114+
motorController.SetDuration(30);
115+
}
116+
}
117+
break;
118+
}
119+
}
120+
return running;
121+
}
122+
123+
void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) {
124+
switch (event) {
125+
case LV_EVENT_VALUE_CHANGED: {
126+
if (obj == bpmArc) {
127+
bpm = lv_arc_get_value(bpmArc);
128+
lv_label_set_text_fmt(bpmValue, "%03d", bpm);
129+
} else if (obj == bpbDropdown) {
130+
bpb = lv_dropdown_get_selected(obj) + 1;
131+
}
132+
break;
133+
}
134+
case LV_EVENT_PRESSED: {
135+
if (obj == bpmTap) {
136+
float timeDelta = calculateDelta(tappedTime, xTaskGetTickCount());
137+
if (tappedTime == 0 || timeDelta > 3) {
138+
tappedTime = xTaskGetTickCount();
139+
} else {
140+
bpm = ceil(60.0 / timeDelta);
141+
lv_arc_set_value(bpmArc, bpm);
142+
lv_label_set_text_fmt(bpmValue, "%03d", bpm);
143+
tappedTime = xTaskGetTickCount();
144+
}
145+
}
146+
break;
147+
}
148+
case LV_EVENT_CLICKED: {
149+
if (obj == playPause) {
150+
currentState = (currentState == States::Stopped ? States::Running : States::Stopped);
151+
switch (currentState) {
152+
case States::Stopped: {
153+
lv_label_set_text(playPauseLabel, Symbols::play);
154+
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::EnableSleeping);
155+
break;
156+
}
157+
case States::Running: {
158+
lv_label_set_text(playPauseLabel, Symbols::pause);
159+
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::DisableSleeping);
160+
startTime = xTaskGetTickCount();
161+
counter = 1;
162+
break;
163+
}
164+
}
165+
}
166+
break;
167+
}
168+
}
169+
}

src/displayapp/screens/Metronome.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#pragma once
2+
3+
#include "systemtask/SystemTask.h"
4+
#include "components/motor/MotorController.h"
5+
6+
#include <array>
7+
8+
namespace Pinetime::Applications::Screens {
9+
10+
class Metronome : public Screen {
11+
public:
12+
Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask);
13+
~Metronome() override;
14+
bool Refresh() override;
15+
bool OnTouchEvent(TouchEvents event) override;
16+
void OnEvent(lv_obj_t* obj, lv_event_t event);
17+
enum class States { Running, Stopped };
18+
19+
private:
20+
bool running;
21+
States currentState;
22+
TickType_t startTime;
23+
TickType_t tappedTime = 0;
24+
Controllers::MotorController& motorController;
25+
System::SystemTask& systemTask;
26+
uint16_t bpm = 120;
27+
uint8_t bpb = 4;
28+
uint8_t counter = 1;
29+
30+
lv_obj_t *bpmArc, *bpmTap, *bpmValue, *bpmLegend;
31+
lv_obj_t *bpbDropdown, *bpbLegend;
32+
lv_obj_t *playPause, *playPauseLabel;
33+
};
34+
}

0 commit comments

Comments
 (0)