diff --git a/CMakeLists.txt b/CMakeLists.txt index 83c6e98db8..41989489bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release") project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM) set(CMAKE_C_STANDARD 99) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 20) # set(CMAKE_GENERATOR "Unix Makefiles") set(CMAKE_C_EXTENSIONS OFF) diff --git a/doc/code/Apps.md b/doc/code/Apps.md index 190ad68385..714e004561 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -9,49 +9,95 @@ This page will teach you: The user interface of InfiniTime is made up of **screens**. Screens that are opened from the app launcher are considered **apps**. -Every app in InfiniTime is it's own class. +Every app in InfiniTime is its own class. An instance of the class is created when the app is launched, and destroyed when the user exits the app. -Apps run inside the "displayapp" task (briefly discussed [here](./Intro.md)). +Apps run inside the `DisplayApp` task (briefly discussed [here](./Intro.md)). Apps are responsible for everything drawn on the screen when they are running. -By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected. +Apps can be refreshed periodically and reacts to external events (touch or button). ## Interface -Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`. -The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen. -Other parameters should be references to controllers that the app needs. -A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping). -App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events. -If an app only needs to display some text and do something upon a touch screen button press, -it does not need to override any of these functions, as LVGL can also handle touch events for you. -If you have any doubts, you can always look at how the other apps function for reference. +Every app class is declared inside the namespace `Pinetime::Applications::Screens` +and inherits +from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h). -### Continuous updating +Each app defines its own constructor. +The constructors mostly take references to InfiniTime `Controllers` (ex: Alarm, DateTime, BLE services, Settings,...) +the app needs for its operations. The constructor is responsible for initializing the UI of the app. -If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class -and calling `lv_task_create` inside the constructor. +The **destructor** cleans up LVGL and restores any changes (for example re-enable sleeping). -An example call could look like this: +App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` +and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events. -```cpp -taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); +Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`) +that will call the method `Refresh()` periodically. + +## App types + +There are basically 2 types of applications : **system** apps and **user** apps. + +**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps. +The watchfaces, settings, notifications and the application launcher are examples of such system applications. + +**User** applications are optionally built into the firmware. They extend the functionalities of the system. + +The distinction between **system** and **user** applications allows for more flexibility and customization. +This allows to easily select which user applications must be built into the firmware +without overflowing the system memory. + +## Apps initialization + +Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`. +This method simply call the creates an instance of the class that corresponds to the app specified in parameters. + +The constructor of **system** apps is called directly. If the application is a **user** app, +the corresponding `AppDescription` is first retrieved from `userApps` +and then the function `create` is called to create an instance of the app. + +## User application selection at build time + +The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()` +in `UserApps.h`. This method takes the list of applications that must be built into the firmware image. +This list of applications is defined as a list `Apps` enum values named `UserAppTypes` in `Apps.h`. +For each application listed in `UserAppTypes`, an entry of type `AppDescription` is added to the array `userApps`. +This entry is created by using the information provided by a template `AppTraits` +that is customized for every user application. + +Here is an example of an AppTraits customized for the Alarm application. +It defines the type of application, its icon and a function that returns an instance of the application. + +```c++ +template <> +struct AppTraits { + static constexpr Apps app = Apps::Alarm; + static constexpr const char* icon = Screens::Symbols::clock; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::Alarm(controllers.alarmController, + controllers.settingsController.GetClockType(), + *controllers.systemTask, + controllers.motorController); + }; +}; ``` -With `taskRefresh` being a member variable of your class and of type `lv_task_t*`. -Remember to delete the task again using `lv_task_del`. -The function `RefreshTaskCallback` is inherited from `Screen` and just calls your `Refresh` function. +This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher` +to list all available applications. ## Creating your own app -A minimal app could look like this: +A minimal user app could look like this: MyApp.h: ```cpp #pragma once +#include "displayapp/Apps.h" #include "displayapp/screens/Screen.h" -#include +#include "displayapp/Controllers.h" +#include "Symbols.h" namespace Pinetime { namespace Applications { @@ -62,6 +108,15 @@ namespace Pinetime { ~MyApp() override; }; } + + template <> + struct AppTraits { + static constexpr Apps app = Apps::MyApp; + static constexpr const char* icon = Screens::Symbol::myApp; + static Screens::Screens* Create(AppController& controllers) { + return new Screens::MyApp(); + } + }; } } ``` @@ -70,7 +125,6 @@ MyApp.cpp: ```cpp #include "displayapp/screens/MyApp.h" -#include "displayapp/DisplayApp.h" using namespace Pinetime::Applications::Screens; @@ -86,20 +140,24 @@ MyApp::~MyApp() { } ``` -Both of these files should be in [displayapp/screens/](/src/displayapp/screens/) -or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app. +Both of these files should be in [displayapp/screens/](/src/displayapp/screens/). Now we have our very own app, but InfiniTime does not know about it yet. -The first step is to include your MyApp.cpp (or any new cpp files for that matter) +The first step is to include your `MyApp.cpp` (or any new cpp files for that matter) in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt). -The next step to making it launchable is to give your app an id. +The next step to making it launch-able is to give your app an id. To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)). -Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp). -Now, go to the function `DisplayApp::LoadScreen` and add another case to the switch statement. +Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` +to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp). + +If your application is a **system** application, go to the function `DisplayApp::LoadScreen` +and add another case to the switch statement. The case will be the id you gave your app earlier. If your app needs any additional arguments, this is the place to pass them. -If you want to add your app in the app launcher, add your app in [displayapp/screens/ApplicationList.h](/src/displayapp/screens/ApplicationList.h) to the array containing the applications and their corresponding symbol. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.h](/src/displayapp/screens/settings/Settings.h). +If your application is a **user** application, you don't need to add anything in DisplayApp, +everything will be automatically generated for you. +The user application will also be automatically be added to the app launcher menu. You should now be able to [build](../buildAndProgram.md) the firmware and flash it to your PineTime. Yay! diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8e8e96863b..dc718bab13 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -394,7 +394,6 @@ list(APPEND SOURCE_FILES displayapp/screens/Notifications.cpp displayapp/screens/Twos.cpp displayapp/screens/HeartRate.cpp - displayapp/screens/Motion.cpp displayapp/screens/FlashLight.cpp displayapp/screens/List.cpp displayapp/screens/CheckboxList.cpp diff --git a/src/components/gfx/Gfx.cpp b/src/components/gfx/Gfx.cpp index 3eaaa3fecc..baa6486a4a 100644 --- a/src/components/gfx/Gfx.cpp +++ b/src/components/gfx/Gfx.cpp @@ -141,7 +141,7 @@ void Gfx::SetBackgroundColor(uint16_t color) { bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) { if (!state.busy) return false; - state.remainingIterations--; + state.remainingIterations = state.remainingIterations - 1; if (state.remainingIterations == 0) { state.busy = false; NotifyEndOfTransfer(state.taskToNotify); @@ -170,7 +170,7 @@ bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) { size = bytes_in_line * 8 * 2; } - state.currentIteration++; + state.currentIteration = state.currentIteration + 1; return true; } diff --git a/src/displayapp/Apps.h b/src/displayapp/Apps.h index f253bc0387..0dd8157e2d 100644 --- a/src/displayapp/Apps.h +++ b/src/displayapp/Apps.h @@ -1,5 +1,5 @@ #pragma once - +#include namespace Pinetime { namespace Applications { enum class Apps { @@ -37,7 +37,33 @@ namespace Pinetime { SettingChimes, SettingShakeThreshold, SettingBluetooth, - Error + Error, + Weather }; + + template + struct AppTraits {}; + + template + struct TypeList { + static constexpr size_t Count = sizeof...(As); + }; + + using UserAppTypes = TypeList; } } diff --git a/src/displayapp/Controllers.h b/src/displayapp/Controllers.h new file mode 100644 index 0000000000..df6b2284b4 --- /dev/null +++ b/src/displayapp/Controllers.h @@ -0,0 +1,56 @@ +#pragma once + +namespace Pinetime { + namespace Applications { + class DisplayApp; + } + + namespace Components { + class LittleVgl; + } + + namespace Controllers { + class Battery; + class Ble; + class DateTime; + class NotificationManager; + class HeartRateController; + class Settings; + class MotorController; + class MotionController; + class AlarmController; + class BrightnessController; + class WeatherService; + class FS; + class Timer; + class MusicService; + class NavigationService; + } + + namespace System { + class SystemTask; + } + + namespace Applications { + struct AppControllers { + const Pinetime::Controllers::Battery& batteryController; + const Pinetime::Controllers::Ble& bleController; + Pinetime::Controllers::DateTime& dateTimeController; + Pinetime::Controllers::NotificationManager& notificationManager; + Pinetime::Controllers::HeartRateController& heartRateController; + Pinetime::Controllers::Settings& settingsController; + Pinetime::Controllers::MotorController& motorController; + Pinetime::Controllers::MotionController& motionController; + Pinetime::Controllers::AlarmController& alarmController; + Pinetime::Controllers::BrightnessController& brightnessController; + Pinetime::Controllers::WeatherService* weatherController; + Pinetime::Controllers::FS& filesystem; + Pinetime::Controllers::Timer& timer; + Pinetime::System::SystemTask* systemTask; + Pinetime::Applications::DisplayApp* displayApp; + Pinetime::Components::LittleVgl& lvgl; + Pinetime::Controllers::MusicService* musicService; + Pinetime::Controllers::NavigationService* navigationService; + }; + } +} diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 682ea12cb3..98be6324a9 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -50,6 +50,7 @@ #include "displayapp/screens/settings/SettingBluetooth.h" #include "libs/lv_conf.h" +#include "UserApps.h" using namespace Pinetime::Applications; using namespace Pinetime::Applications::Display; @@ -96,7 +97,25 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd, touchHandler {touchHandler}, filesystem {filesystem}, lvgl {lcd, filesystem}, - timer(this, TimerCallback) { + timer(this, TimerCallback), + controllers {batteryController, + bleController, + dateTimeController, + notificationManager, + heartRateController, + settingsController, + motorController, + motionController, + alarmController, + brightnessController, + nullptr, + filesystem, + timer, + nullptr, + this, + lvgl, + nullptr, + nullptr} { } void DisplayApp::Start(System::BootErrors error) { @@ -402,14 +421,20 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio SetFullRefresh(direction); switch (app) { - case Apps::Launcher: - currentScreen = - std::make_unique(this, settingsController, batteryController, bleController, dateTimeController, filesystem); - break; - case Apps::Motion: - // currentScreen = std::make_unique(motionController); - // break; - case Apps::None: + case Apps::Launcher: { + std::array apps; + int i = 0; + for (const auto& userApp : userApps) { + apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true}; + } + currentScreen = std::make_unique(this, + settingsController, + batteryController, + bleController, + dateTimeController, + filesystem, + std::move(apps)); + } break; case Apps::Clock: currentScreen = std::make_unique(dateTimeController, batteryController, @@ -421,7 +446,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio systemTask->nimble().weather(), filesystem); break; - case Apps::Error: currentScreen = std::make_unique(bootError); break; @@ -453,14 +477,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio *systemTask, Screens::Notifications::Modes::Preview); break; - case Apps::Timer: - currentScreen = std::make_unique(timer); - break; - case Apps::Alarm: - currentScreen = std::make_unique(alarmController, settingsController.GetClockType(), *systemTask, motorController); - break; - - // Settings case Apps::QuickSettings: currentScreen = std::make_unique(this, batteryController, @@ -516,38 +532,25 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio case Apps::FlashLight: currentScreen = std::make_unique(*systemTask, brightnessController); break; - case Apps::StopWatch: - currentScreen = std::make_unique(*systemTask); - break; - case Apps::Twos: - currentScreen = std::make_unique(); - break; - case Apps::Paint: - currentScreen = std::make_unique(lvgl, motorController); - break; - case Apps::Paddle: - currentScreen = std::make_unique(lvgl); - break; - case Apps::Music: - currentScreen = std::make_unique(systemTask->nimble().music()); - break; - case Apps::Navigation: - currentScreen = std::make_unique(systemTask->nimble().navigation()); - break; - case Apps::HeartRate: - currentScreen = std::make_unique(heartRateController, *systemTask); - break; - case Apps::Metronome: - currentScreen = std::make_unique(motorController, *systemTask); - break; - /* Weather debug app - case Apps::Weather: - currentScreen = std::make_unique(this, systemTask->nimble().weather()); - break; - */ - case Apps::Steps: - currentScreen = std::make_unique(motionController, settingsController); + default: { + const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) { + return appDescription.app == app; + }); + if (d != userApps.end()) + currentScreen.reset(d->create(controllers)); + else { + currentScreen = std::make_unique(dateTimeController, + batteryController, + bleController, + notificationManager, + settingsController, + heartRateController, + motionController, + systemTask->nimble().weather(), + filesystem); + } break; + } } currentApp = app; } @@ -605,6 +608,19 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) { void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { this->systemTask = systemTask; + this->controllers.systemTask = systemTask; +} + +void DisplayApp::Register(Pinetime::Controllers::WeatherService* weatherService) { + this->controllers.weatherController = weatherService; +} + +void DisplayApp::Register(Pinetime::Controllers::MusicService* musicService) { + this->controllers.musicService = musicService; +} + +void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationService) { + this->controllers.navigationService = NavigationService; } void DisplayApp::ApplyBrightness() { diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h index f537651dbf..7dbac850aa 100644 --- a/src/displayapp/DisplayApp.h +++ b/src/displayapp/DisplayApp.h @@ -20,6 +20,7 @@ #include "BootErrors.h" #include "utility/StaticStack.h" +#include "displayapp/Controllers.h" namespace Pinetime { @@ -73,6 +74,9 @@ namespace Pinetime { void SetFullRefresh(FullRefreshDirections direction); void Register(Pinetime::System::SystemTask* systemTask); + void Register(Pinetime::Controllers::WeatherService* weatherService); + void Register(Pinetime::Controllers::MusicService* musicService); + void Register(Pinetime::Controllers::NavigationService* NavigationService); private: Pinetime::Drivers::St7789& lcd; @@ -96,6 +100,7 @@ namespace Pinetime { Pinetime::Components::LittleVgl lvgl; Pinetime::Controllers::Timer timer; + AppControllers controllers; TaskHandle_t taskHandle; States state = States::Running; diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp index de165c2922..9fe59c40a8 100644 --- a/src/displayapp/DisplayAppRecovery.cpp +++ b/src/displayapp/DisplayAppRecovery.cpp @@ -121,3 +121,12 @@ void DisplayApp::PushMessage(Display::Messages msg) { void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) { } + +void DisplayApp::Register(Pinetime::Controllers::WeatherService* /*weatherService*/) { +} + +void DisplayApp::Register(Pinetime::Controllers::MusicService* /*musicService*/) { +} + +void DisplayApp::Register(Pinetime::Controllers::NavigationService* /*NavigationService*/) { +} diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h index 3ce9518773..41aedb1793 100644 --- a/src/displayapp/DisplayAppRecovery.h +++ b/src/displayapp/DisplayAppRecovery.h @@ -34,6 +34,9 @@ namespace Pinetime { class AlarmController; class BrightnessController; class FS; + class WeatherService; + class MusicService; + class NavigationService; } namespace System { @@ -66,6 +69,9 @@ namespace Pinetime { void PushMessage(Pinetime::Applications::Display::Messages msg); void Register(Pinetime::System::SystemTask* systemTask); + void Register(Pinetime::Controllers::WeatherService* weatherService); + void Register(Pinetime::Controllers::MusicService* musicService); + void Register(Pinetime::Controllers::NavigationService* NavigationService); private: TaskHandle_t taskHandle; diff --git a/src/displayapp/UserApps.h b/src/displayapp/UserApps.h new file mode 100644 index 0000000000..0ed9d60294 --- /dev/null +++ b/src/displayapp/UserApps.h @@ -0,0 +1,36 @@ +#pragma once +#include "Apps.h" +#include "Controllers.h" + +#include "displayapp/screens/Alarm.h" +#include "displayapp/screens/Timer.h" +#include "displayapp/screens/Twos.h" +#include "displayapp/screens/Tile.h" +#include "displayapp/screens/ApplicationList.h" +#include "displayapp/screens/Clock.h" + +namespace Pinetime { + namespace Applications { + namespace Screens { + class Screen; + } + + struct AppDescription { + Apps app; + const char* icon; + Screens::Screen* (*create)(AppControllers& controllers); + }; + + template + consteval AppDescription CreateAppDescription() { + return {AppTraits::app, AppTraits::icon, &AppTraits::Create}; + } + + template