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
This commit is contained in:
parent
58a8507b3d
commit
c575754b42
|
@ -396,6 +396,7 @@ list(APPEND SOURCE_FILES
|
|||
displayapp/screens/FirmwareUpdate.cpp
|
||||
displayapp/screens/Music.cpp
|
||||
displayapp/screens/Navigation.cpp
|
||||
displayapp/screens/Metronome.cpp
|
||||
displayapp/screens/Motion.cpp
|
||||
displayapp/screens/FirmwareValidation.cpp
|
||||
displayapp/screens/ApplicationList.cpp
|
||||
|
@ -592,6 +593,7 @@ set(INCLUDE_FILES
|
|||
displayapp/Apps.h
|
||||
displayapp/screens/Notifications.h
|
||||
displayapp/screens/HeartRate.h
|
||||
displayapp/screens/Metronome.h
|
||||
displayapp/screens/Motion.h
|
||||
displayapp/screens/Timer.h
|
||||
drivers/St7789.h
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Pinetime {
|
|||
HeartRate,
|
||||
Navigation,
|
||||
StopWatch,
|
||||
Metronome,
|
||||
Motion,
|
||||
Steps,
|
||||
QuickSettings,
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "displayapp/screens/Paddle.h"
|
||||
#include "displayapp/screens/StopWatch.h"
|
||||
#include "displayapp/screens/Meter.h"
|
||||
#include "displayapp/screens/Metronome.h"
|
||||
#include "displayapp/screens/Music.h"
|
||||
#include "displayapp/screens/Navigation.h"
|
||||
#include "displayapp/screens/Notifications.h"
|
||||
|
@ -318,7 +319,7 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
|
|||
currentScreen = std::make_unique<Screens::SettingDisplay>(this, settingsController);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
case Apps::SettingSteps:
|
||||
case Apps::SettingSteps:
|
||||
currentScreen = std::make_unique<Screens::SettingSteps>(this, settingsController);
|
||||
ReturnApp(Apps::Settings, FullRefreshDirections::Down, TouchEvents::SwipeDown);
|
||||
break;
|
||||
|
@ -356,10 +357,13 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
|
|||
case Apps::HeartRate:
|
||||
currentScreen = std::make_unique<Screens::HeartRate>(this, heartRateController, *systemTask);
|
||||
break;
|
||||
case Apps::Metronome:
|
||||
currentScreen = std::make_unique<Screens::Metronome>(this, motorController, systemTask);
|
||||
break;
|
||||
case Apps::Motion:
|
||||
currentScreen = std::make_unique<Screens::Motion>(this, motionController);
|
||||
break;
|
||||
case Apps::Steps:
|
||||
case Apps::Steps:
|
||||
currentScreen = std::make_unique<Screens::Steps>(this, motionController, settingsController);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ static lv_style_t style_sw_bg;
|
|||
static lv_style_t style_sw_indic;
|
||||
static lv_style_t style_sw_knob;
|
||||
static lv_style_t style_arc_bg;
|
||||
static lv_style_t style_arc_knob;
|
||||
static lv_style_t style_arc_indic;
|
||||
static lv_style_t style_table_cell;
|
||||
static lv_style_t style_pad_small;
|
||||
|
@ -191,6 +192,7 @@ static void basic_init(void) {
|
|||
lv_style_set_text_line_space(&style_ddlist_list, LV_STATE_DEFAULT, LV_VER_RES / 25);
|
||||
lv_style_set_shadow_width(&style_ddlist_list, LV_STATE_DEFAULT, LV_VER_RES / 20);
|
||||
lv_style_set_shadow_color(&style_ddlist_list, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
|
||||
lv_style_set_bg_color(&style_ddlist_list, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
|
||||
|
||||
style_init_reset(&style_ddlist_selected);
|
||||
lv_style_set_bg_opa(&style_ddlist_selected, LV_STATE_DEFAULT, LV_OPA_COVER);
|
||||
|
@ -239,6 +241,13 @@ static void basic_init(void) {
|
|||
lv_style_set_line_color(&style_arc_bg, LV_STATE_DEFAULT, LV_PINETIME_GRAY);
|
||||
lv_style_set_line_width(&style_arc_bg, LV_STATE_DEFAULT, LV_DPX(25));
|
||||
lv_style_set_line_rounded(&style_arc_bg, LV_STATE_DEFAULT, true);
|
||||
lv_style_set_pad_all(&style_arc_bg, LV_STATE_DEFAULT, LV_DPX(5));
|
||||
|
||||
lv_style_reset(&style_arc_knob);
|
||||
lv_style_set_radius(&style_arc_knob, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
|
||||
lv_style_set_bg_opa(&style_arc_knob, LV_STATE_DEFAULT, LV_OPA_COVER);
|
||||
lv_style_set_bg_color(&style_arc_knob, LV_STATE_DEFAULT, LV_PINETIME_LIGHT_GRAY);
|
||||
lv_style_set_pad_all(&style_arc_knob, LV_STATE_DEFAULT, LV_DPX(5));
|
||||
|
||||
style_init_reset(&style_table_cell);
|
||||
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) {
|
|||
lv_obj_clean_style_list(obj, LV_ARC_PART_INDIC);
|
||||
list = lv_obj_get_style_list(obj, LV_ARC_PART_INDIC);
|
||||
_lv_style_list_add_style(list, &style_arc_indic);
|
||||
|
||||
lv_obj_clean_style_list(obj, LV_ARC_PART_KNOB);
|
||||
list = lv_obj_get_style_list(obj, LV_ARC_PART_KNOB);
|
||||
_lv_style_list_add_style(list, &style_arc_knob);
|
||||
break;
|
||||
|
||||
case LV_THEME_SWITCH:
|
||||
|
|
|
@ -63,7 +63,7 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen2() {
|
|||
{Symbols::paddle, Apps::Paddle},
|
||||
{"2", Apps::Twos},
|
||||
{"M", Apps::Motion},
|
||||
{"", Apps::None},
|
||||
{"b", Apps::Metronome},
|
||||
{"", Apps::None},
|
||||
}};
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
#include "Metronome.h"
|
||||
|
||||
#include "Screen.h"
|
||||
#include "Symbols.h"
|
||||
#include "lvgl/lvgl.h"
|
||||
#include "FreeRTOSConfig.h"
|
||||
#include "task.h"
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
using namespace Pinetime::Applications::Screens;
|
||||
|
||||
namespace {
|
||||
float calculateDelta(const TickType_t startTime, const TickType_t currentTime) {
|
||||
TickType_t delta = 0;
|
||||
// Take care of overflow
|
||||
if (startTime > currentTime) {
|
||||
delta = 0xffffffff - startTime;
|
||||
delta += (currentTime + 1);
|
||||
} else {
|
||||
delta = currentTime - startTime;
|
||||
}
|
||||
return static_cast<float>(delta) / static_cast<float>(configTICK_RATE_HZ);
|
||||
}
|
||||
|
||||
static void eventHandler(lv_obj_t* obj, lv_event_t event) {
|
||||
Metronome* screen = static_cast<Metronome*>(obj->user_data);
|
||||
screen->OnEvent(obj, event);
|
||||
}
|
||||
|
||||
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) {
|
||||
lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
|
||||
lv_obj_set_style_local_text_font(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
|
||||
lv_obj_set_style_local_text_color(label, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
|
||||
lv_label_set_text(label, name);
|
||||
lv_obj_align(label, reference, align, x, y);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
Metronome::Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask)
|
||||
: Screen(app), running {true}, currentState {States::Stopped}, startTime {}, motorController {motorController}, systemTask {systemTask} {
|
||||
|
||||
bpmArc = lv_arc_create(lv_scr_act(), nullptr);
|
||||
bpmArc->user_data = this;
|
||||
lv_obj_set_event_cb(bpmArc, eventHandler);
|
||||
lv_arc_set_bg_angles(bpmArc, 0, 270);
|
||||
lv_arc_set_rotation(bpmArc, 135);
|
||||
lv_arc_set_range(bpmArc, 40, 220);
|
||||
lv_arc_set_value(bpmArc, bpm);
|
||||
lv_obj_set_size(bpmArc, 210, 210);
|
||||
lv_arc_set_adjustable(bpmArc, true);
|
||||
lv_obj_align(bpmArc, lv_scr_act(), LV_ALIGN_IN_TOP_MID, 0, 7);
|
||||
|
||||
bpmValue = createLabel(std::to_string(lv_arc_get_value(bpmArc)).c_str(), bpmArc, LV_ALIGN_IN_TOP_MID, &jetbrains_mono_76, 0, 55);
|
||||
bpmLegend = createLabel("bpm", bpmValue, LV_ALIGN_OUT_BOTTOM_MID, &jetbrains_mono_bold_20, 0, 0);
|
||||
|
||||
bpmTap = lv_btn_create(lv_scr_act(), nullptr);
|
||||
bpmTap->user_data = this;
|
||||
lv_obj_set_event_cb(bpmTap, eventHandler);
|
||||
lv_obj_set_style_local_bg_opa(bpmTap, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, LV_OPA_TRANSP);
|
||||
lv_obj_set_height(bpmTap, 80);
|
||||
lv_obj_align(bpmTap, bpmValue, LV_ALIGN_IN_TOP_MID, 0, 0);
|
||||
|
||||
bpbDropdown = lv_dropdown_create(lv_scr_act(), nullptr);
|
||||
bpbDropdown->user_data = this;
|
||||
lv_obj_set_event_cb(bpbDropdown, eventHandler);
|
||||
lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_MAIN, LV_STATE_DEFAULT, 20);
|
||||
lv_obj_set_style_local_pad_left(bpbDropdown, LV_DROPDOWN_PART_LIST, LV_STATE_DEFAULT, 20);
|
||||
lv_obj_align(bpbDropdown, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 15, -4);
|
||||
lv_dropdown_set_options(bpbDropdown, "1\n2\n3\n4\n5\n6\n7\n8\n9");
|
||||
lv_dropdown_set_selected(bpbDropdown, bpb - 1);
|
||||
bpbLegend = lv_label_create(bpbDropdown, nullptr);
|
||||
lv_label_set_text(bpbLegend, "bpb");
|
||||
lv_obj_align(bpbLegend, bpbDropdown, LV_ALIGN_IN_RIGHT_MID, -15, 0);
|
||||
|
||||
playPause = lv_btn_create(lv_scr_act(), nullptr);
|
||||
playPause->user_data = this;
|
||||
lv_obj_set_event_cb(playPause, eventHandler);
|
||||
lv_obj_align(playPause, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -15, -10);
|
||||
lv_obj_set_height(playPause, 39);
|
||||
playPauseLabel = lv_label_create(playPause, nullptr);
|
||||
lv_label_set_text(playPauseLabel, Symbols::play);
|
||||
|
||||
app->SetTouchMode(DisplayApp::TouchModes::Polling);
|
||||
}
|
||||
|
||||
Metronome::~Metronome() {
|
||||
app->SetTouchMode(DisplayApp::TouchModes::Gestures);
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::EnableSleeping);
|
||||
lv_obj_clean(lv_scr_act());
|
||||
}
|
||||
|
||||
bool Metronome::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Metronome::Refresh() {
|
||||
switch (currentState) {
|
||||
case States::Stopped: {
|
||||
break;
|
||||
}
|
||||
case States::Running: {
|
||||
if (calculateDelta(startTime, xTaskGetTickCount()) >= (60.0 / bpm)) {
|
||||
counter--;
|
||||
startTime -= 60.0 / bpm;
|
||||
startTime = xTaskGetTickCount();
|
||||
if (counter == 0) {
|
||||
counter = bpb;
|
||||
motorController.SetDuration(90);
|
||||
} else {
|
||||
motorController.SetDuration(30);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return running;
|
||||
}
|
||||
|
||||
void Metronome::OnEvent(lv_obj_t* obj, lv_event_t event) {
|
||||
switch (event) {
|
||||
case LV_EVENT_VALUE_CHANGED: {
|
||||
if (obj == bpmArc) {
|
||||
bpm = lv_arc_get_value(bpmArc);
|
||||
lv_label_set_text_fmt(bpmValue, "%03d", bpm);
|
||||
} else if (obj == bpbDropdown) {
|
||||
bpb = lv_dropdown_get_selected(obj) + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LV_EVENT_PRESSED: {
|
||||
if (obj == bpmTap) {
|
||||
float timeDelta = calculateDelta(tappedTime, xTaskGetTickCount());
|
||||
if (tappedTime == 0 || timeDelta > 3) {
|
||||
tappedTime = xTaskGetTickCount();
|
||||
} else {
|
||||
bpm = ceil(60.0 / timeDelta);
|
||||
lv_arc_set_value(bpmArc, bpm);
|
||||
lv_label_set_text_fmt(bpmValue, "%03d", bpm);
|
||||
tappedTime = xTaskGetTickCount();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LV_EVENT_CLICKED: {
|
||||
if (obj == playPause) {
|
||||
currentState = (currentState == States::Stopped ? States::Running : States::Stopped);
|
||||
switch (currentState) {
|
||||
case States::Stopped: {
|
||||
lv_label_set_text(playPauseLabel, Symbols::play);
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::EnableSleeping);
|
||||
break;
|
||||
}
|
||||
case States::Running: {
|
||||
lv_label_set_text(playPauseLabel, Symbols::pause);
|
||||
systemTask.PushMessage(Pinetime::System::SystemTask::Messages::DisableSleeping);
|
||||
startTime = xTaskGetTickCount();
|
||||
counter = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "systemtask/SystemTask.h"
|
||||
#include "components/motor/MotorController.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Pinetime::Applications::Screens {
|
||||
|
||||
class Metronome : public Screen {
|
||||
public:
|
||||
Metronome(DisplayApp* app, Controllers::MotorController& motorController, System::SystemTask& systemTask);
|
||||
~Metronome() override;
|
||||
bool Refresh() override;
|
||||
bool OnTouchEvent(TouchEvents event) override;
|
||||
void OnEvent(lv_obj_t* obj, lv_event_t event);
|
||||
enum class States { Running, Stopped };
|
||||
|
||||
private:
|
||||
bool running;
|
||||
States currentState;
|
||||
TickType_t startTime;
|
||||
TickType_t tappedTime = 0;
|
||||
Controllers::MotorController& motorController;
|
||||
System::SystemTask& systemTask;
|
||||
uint16_t bpm = 120;
|
||||
uint8_t bpb = 4;
|
||||
uint8_t counter = 1;
|
||||
|
||||
lv_obj_t *bpmArc, *bpmTap, *bpmValue, *bpmLegend;
|
||||
lv_obj_t *bpbDropdown, *bpbLegend;
|
||||
lv_obj_t *playPause, *playPauseLabel;
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue