From 14d002d7f016679b303334fb4ca561ec2c86ff5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sun, 24 Dec 2023 11:44:37 +0100 Subject: [PATCH 1/4] Watch face selection using CMake The list of watch face to build into the firmware is now set by CMake (-DENABLE_WATCHFACES). Fix SettingWatchFace : convert to index to/from WatchFace when needed. --- src/displayapp/DisplayApp.cpp | 5 ++- src/displayapp/apps/Apps.h.in | 7 +--- src/displayapp/apps/CMakeLists.txt | 6 +++ .../screens/settings/SettingWatchFace.cpp | 42 ++++++++++++++++--- .../screens/settings/SettingWatchFace.h | 10 ++++- 5 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 2792009f05..938d1179a8 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -489,10 +489,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio currentScreen = std::make_unique(this, settingsController); break; case Apps::SettingWatchFace: { - std::array items; + std::array items; int i = 0; for (const auto& userWatchFace : userWatchFaces) { - items[i++] = Screens::CheckboxList::Item {userWatchFace.name, userWatchFace.isAvailable(controllers.filesystem)}; + items[i++] = + Screens::SettingWatchFace::Item {userWatchFace.name, userWatchFace.watchFace, userWatchFace.isAvailable(controllers.filesystem)}; } currentScreen = std::make_unique(this, std::move(items), settingsController, filesystem); } break; diff --git a/src/displayapp/apps/Apps.h.in b/src/displayapp/apps/Apps.h.in index 11fe17b575..e6e8d7dcd9 100644 --- a/src/displayapp/apps/Apps.h.in +++ b/src/displayapp/apps/Apps.h.in @@ -71,12 +71,7 @@ namespace Pinetime { static constexpr size_t Count = sizeof...(Ws); }; - using UserWatchFaceTypes = WatchFaceTypeList; + using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>; static_assert(UserWatchFaceTypes::Count >= 1); } diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index ddf95171fb..9e424fd39c 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -4,6 +4,12 @@ else () set(USERAPP_TYPES "Apps::Navigation, Apps::StopWatch, Apps::Alarm, Apps::Timer, Apps::Steps, Apps::HeartRate, Apps::Music, Apps::Paint, Apps::Paddle, Apps::Twos, Apps::Metronome" CACHE STRING "List of user apps to build into the firmware") endif () +if(DEFINED ENABLE_WATCHFACES) + set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") +else() + set(WATCHFACE_TYPES "WatchFace::Digital, WatchFace::Analog, WatchFace::PineTimeStyle, WatchFace::Terminal, WatchFace::Infineat, WatchFace::CasioStyleG7710" CACHE STRING "List of watch faces to build into the firmware") +endif() + add_library(infinitime_apps INTERFACE) target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h") target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/") diff --git a/src/displayapp/screens/settings/SettingWatchFace.cpp b/src/displayapp/screens/settings/SettingWatchFace.cpp index f052573c36..e01e9f847d 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.cpp +++ b/src/displayapp/screens/settings/SettingWatchFace.cpp @@ -9,6 +9,37 @@ using namespace Pinetime::Applications::Screens; constexpr const char* SettingWatchFace::title; constexpr const char* SettingWatchFace::symbol; +namespace { + uint32_t IndexOf(const std::array& watchfaces, + Pinetime::Applications::WatchFace watchface) { + size_t index = 0; + auto found = std::find_if(watchfaces.begin(), + watchfaces.end(), + [&index, &watchface](const Pinetime::Applications::Screens::SettingWatchFace::Item& item) { + const bool result = item.watchface == watchface; + if (!result) { + index++; + } + return result; + }); + if (found == watchfaces.end()) { + index = 0; + } + + return index; + } + + Pinetime::Applications::WatchFace IndexToWatchFace(const std::array& watchfaces, + size_t index) { + if (index >= watchfaces.size()) { + return watchfaces[0].watchface; + } + return watchfaces[index].watchface; + } +} + auto SettingWatchFace::CreateScreenList() const { std::array()>, nScreens> screens; for (size_t i = 0; i < screens.size(); i++) { @@ -20,7 +51,7 @@ auto SettingWatchFace::CreateScreenList() const { } SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app, - std::array&& watchfaceItems, + std::array&& watchfaceItems, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem) : app {app}, @@ -44,7 +75,8 @@ std::unique_ptr SettingWatchFace::CreateScreen(unsigned int screenNum) c if (i + (screenNum * settingsPerScreen) >= watchfaceItems.size()) { watchfacesOnThisScreen[i] = {"", false}; } else { - watchfacesOnThisScreen[i] = watchfaceItems[i + (screenNum * settingsPerScreen)]; + auto& item = watchfaceItems[i + (screenNum * settingsPerScreen)]; + watchfacesOnThisScreen[i] = Screens::CheckboxList::Item {item.name, item.enabled}; } } @@ -53,9 +85,9 @@ std::unique_ptr SettingWatchFace::CreateScreen(unsigned int screenNum) c nScreens, title, symbol, - static_cast(settingsController.GetWatchFace()), - [&settings = settingsController](uint32_t index) { - settings.SetWatchFace(static_cast(index)); + static_cast(IndexOf(watchfaceItems, settingsController.GetWatchFace())), + [this, &settings = settingsController](uint32_t index) { + settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index)); settings.SaveSettings(); }, watchfacesOnThisScreen); diff --git a/src/displayapp/screens/settings/SettingWatchFace.h b/src/displayapp/screens/settings/SettingWatchFace.h index 66559c73cb..4c75b0ab21 100644 --- a/src/displayapp/screens/settings/SettingWatchFace.h +++ b/src/displayapp/screens/settings/SettingWatchFace.h @@ -19,8 +19,14 @@ namespace Pinetime { class SettingWatchFace : public Screen { public: + struct Item { + const char* name; + WatchFace watchface; + bool enabled; + }; + SettingWatchFace(DisplayApp* app, - std::array&& watchfaceItems, + std::array&& watchfaceItems, Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::FS& filesystem); ~SettingWatchFace() override; @@ -33,7 +39,7 @@ namespace Pinetime { std::unique_ptr CreateScreen(unsigned int screenNum) const; static constexpr int settingsPerScreen = 4; - std::array watchfaceItems; + std::array watchfaceItems; static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1; Controllers::Settings& settingsController; From bf56387bc00146edd47aa4f419df8937c2ef89eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sat, 30 Dec 2023 20:46:36 +0100 Subject: [PATCH 2/4] Watch face selection using CMake Update Apps.md to mention the selection of watchfaces using Cmake. --- doc/code/Apps.md | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/doc/code/Apps.md b/doc/code/Apps.md index 6ca844815b..ca7d8bc229 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -35,18 +35,20 @@ that will call the method `Refresh()` periodically. ## App types -There are basically 2 types of applications : **system** apps and **user** apps. +There are basically 3 types of applications : **system** apps and **user** apps and **watchfaces**. **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 +**Watchfaces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware. + +The distinction between **system** apps, **user** apps and watchfaces allows for more flexibility and customization. +This allows to easily select which user applications and watchfaces must be built into the firmware without overflowing the system memory. -## Apps initialization +## Apps and watchfaces 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. @@ -55,6 +57,8 @@ The constructor of **system** apps is called directly. If the application is a * the corresponding `AppDescription` is first retrieved from `userApps` and then the function `create` is called to create an instance of the app. +Watchfaces are handled in a very similar way than the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`. + ## User application selection at build time The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()` @@ -85,6 +89,32 @@ struct AppTraits { This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher` to list all available applications. +## Watchface selection at build time + +The list of available watchface is also generated at build time by the `consteval` +function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps. +Watchfaces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated. +Here is an example of `WatchFaceTraits`: +```c++ + template <> + struct WatchFaceTraits { + static constexpr WatchFace watchFace = WatchFace::Analog; + static constexpr const char* name = "Analog face"; + + static Screens::Screen* Create(AppControllers& controllers) { + return new Screens::WatchFaceAnalog(controllers.dateTimeController, + controllers.batteryController, + controllers.bleController, + controllers.notificationManager, + controllers.settingsController); + }; + + static bool IsAvailable(Pinetime::Controllers::FS& /*filesystem*/) { + return true; + } + }; +``` + ## Creating your own app A minimal user app could look like this: @@ -168,6 +198,14 @@ Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the ap $ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ... ``` +Similarly, the list of watchfaces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake. It must be set with the list of watchfaces that will be built into the firmware. + +Ex: build the firmware with 3 watchfaces : Analog, PineTimeStyle and Infineat: + +```cmake +$ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ... +``` + You should now be able to [build](../buildAndProgram.md) the firmware and flash it to your PineTime. Yay! From ad8e80170dde9f914ef8c88d1d50c4d08588de9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sat, 6 Jan 2024 11:28:48 +0100 Subject: [PATCH 3/4] CMake watch faces selection Improve wording and replace "watchface" by "watch face" in Apps.md. Improve CMake readability regarding watch face selection Co-authored-by: Reinhold Gschweicher --- doc/code/Apps.md | 25 +++++++++++++------------ src/displayapp/apps/CMakeLists.txt | 8 +++++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/doc/code/Apps.md b/doc/code/Apps.md index ca7d8bc229..d97eff4b3b 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -35,20 +35,20 @@ that will call the method `Refresh()` periodically. ## App types -There are basically 3 types of applications : **system** apps and **user** apps and **watchfaces**. +There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**. **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. +The watch faces, 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. -**Watchfaces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware. +**Watch faces** are very similar to the **user** apps, they are optional, but at least one must be built into the firmware. -The distinction between **system** apps, **user** apps and watchfaces allows for more flexibility and customization. -This allows to easily select which user applications and watchfaces must be built into the firmware +The distinction between **system** apps, **user** apps and watch faces allows for more flexibility and customization. +This allows to easily select which user applications and watch faces must be built into the firmware without overflowing the system memory. -## Apps and watchfaces initialization +## Apps and watch faces 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. @@ -57,7 +57,7 @@ The constructor of **system** apps is called directly. If the application is a * the corresponding `AppDescription` is first retrieved from `userApps` and then the function `create` is called to create an instance of the app. -Watchfaces are handled in a very similar way than the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`. +Watch faces are handled in a very similar way as the **user** apps : they are created by `DisplayApp` in the method `DisplayApp::LoadScreen()` when the application type is `Apps::Clock`. ## User application selection at build time @@ -89,11 +89,11 @@ struct AppTraits { This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher` to list all available applications. -## Watchface selection at build time +## Watch face selection at build time -The list of available watchface is also generated at build time by the `consteval` +The list of available watch faces is also generated at build time by the `consteval` function `CreateWatchFaceDescriptions()` in `UserApps.h` in the same way as the **user** apps. -Watchfaces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated. +Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated. Here is an example of `WatchFaceTraits`: ```c++ template <> @@ -198,9 +198,10 @@ Ex : build the firmware with 3 user application : Alarm, Timer and MyApp (the ap $ cmake ... -DENABLE_USERAPPS="Apps::Alarm, Apps::Timer, Apps::MyApp" ... ``` -Similarly, the list of watchfaces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake. It must be set with the list of watchfaces that will be built into the firmware. +Similarly, the list of watch faces is also generated by CMake, so you need to add the variable `ENABLE_WATCHFACES` to the command line of CMake. +It must be set with the comma separated list of watch faces that will be built into the firmware. -Ex: build the firmware with 3 watchfaces : Analog, PineTimeStyle and Infineat: +Ex: build the firmware with 3 watch faces : Analog, PineTimeStyle and Infineat: ```cmake $ cmake ... -DENABLE_WATCHFACES="WatchFace::Analog,WatchFace::PineTimeStyle,WatchFace::Infineat" ... diff --git a/src/displayapp/apps/CMakeLists.txt b/src/displayapp/apps/CMakeLists.txt index 9e424fd39c..9096e810b7 100644 --- a/src/displayapp/apps/CMakeLists.txt +++ b/src/displayapp/apps/CMakeLists.txt @@ -7,7 +7,13 @@ endif () if(DEFINED ENABLE_WATCHFACES) set(WATCHFACE_TYPES ${ENABLE_WATCHFACES} CACHE STRING "List of watch faces to build into the firmware") else() - set(WATCHFACE_TYPES "WatchFace::Digital, WatchFace::Analog, WatchFace::PineTimeStyle, WatchFace::Terminal, WatchFace::Infineat, WatchFace::CasioStyleG7710" CACHE STRING "List of watch faces to build into the firmware") + set(DEFAULT_WATCHFACE_TYPES "WatchFace::Digital") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Analog") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::PineTimeStyle") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Terminal") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::Infineat") + set(DEFAULT_WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}, WatchFace::CasioStyleG7710") + set(WATCHFACE_TYPES "${DEFAULT_WATCHFACE_TYPES}" CACHE STRING "List of watch faces to build into the firmware") endif() add_library(infinitime_apps INTERFACE) From 5cfb95626b6a9981f9377458813f78d8d51ef590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Milants?= Date: Sat, 6 Jan 2024 14:28:34 +0100 Subject: [PATCH 4/4] CMake watch faces selection Documentation : watch faces are not system apps anymore. Co-authored-by: FintasticMan --- doc/code/Apps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/code/Apps.md b/doc/code/Apps.md index d97eff4b3b..b325fe98e1 100644 --- a/doc/code/Apps.md +++ b/doc/code/Apps.md @@ -38,7 +38,7 @@ that will call the method `Refresh()` periodically. There are basically 3 types of applications : **system** apps and **user** apps and **watch faces**. **System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps. -The watch faces, settings, notifications and the application launcher are examples of such system applications. +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.