Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Watch face selection using CMake #1934

Merged
merged 4 commits into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 44 additions & 5 deletions doc/code/Apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 **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.
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
**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 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 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.
Expand All @@ -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.

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

The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
Expand Down Expand Up @@ -85,6 +89,32 @@ struct AppTraits<Apps::Alarm> {
This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
to list all available applications.

## Watch face selection at build time

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.
Watch faces must declare a `WatchFaceTraits` so that the corresponding `WatchFaceDescription` can be generated.
Here is an example of `WatchFaceTraits`:
```c++
template <>
struct WatchFaceTraits<WatchFace::Analog> {
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:
Expand Down Expand Up @@ -168,6 +198,15 @@ 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 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 watch faces : 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!

Expand Down
5 changes: 3 additions & 2 deletions src/displayapp/DisplayApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,10 +489,11 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
currentScreen = std::make_unique<Screens::Settings>(this, settingsController);
break;
case Apps::SettingWatchFace: {
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> items;
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count> 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<Screens::SettingWatchFace>(this, std::move(items), settingsController, filesystem);
} break;
Expand Down
7 changes: 1 addition & 6 deletions src/displayapp/apps/Apps.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,7 @@ namespace Pinetime {
static constexpr size_t Count = sizeof...(Ws);
};

using UserWatchFaceTypes = WatchFaceTypeList<WatchFace::Digital,
WatchFace::Analog,
WatchFace::PineTimeStyle,
WatchFace::Terminal,
WatchFace::Infineat,
WatchFace::CasioStyleG7710>;
using UserWatchFaceTypes = WatchFaceTypeList<@WATCHFACE_TYPES@>;

static_assert(UserWatchFaceTypes::Count >= 1);
}
Expand Down
12 changes: 12 additions & 0 deletions src/displayapp/apps/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ 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(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)
target_sources(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/Apps.h")
target_include_directories(infinitime_apps INTERFACE "${CMAKE_CURRENT_BINARY_DIR}/")
Expand Down
42 changes: 37 additions & 5 deletions src/displayapp/screens/settings/SettingWatchFace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& 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<Pinetime::Applications::Screens::SettingWatchFace::Item,
Pinetime::Applications::UserWatchFaceTypes::Count>& watchfaces,
size_t index) {
if (index >= watchfaces.size()) {
return watchfaces[0].watchface;
}
return watchfaces[index].watchface;
}
}

auto SettingWatchFace::CreateScreenList() const {
std::array<std::function<std::unique_ptr<Screen>()>, nScreens> screens;
for (size_t i = 0; i < screens.size(); i++) {
Expand All @@ -20,7 +51,7 @@ auto SettingWatchFace::CreateScreenList() const {
}

SettingWatchFace::SettingWatchFace(Pinetime::Applications::DisplayApp* app,
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
std::array<Screens::SettingWatchFace::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem)
: app {app},
Expand All @@ -44,7 +75,8 @@ std::unique_ptr<Screen> 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};
}
}

Expand All @@ -53,9 +85,9 @@ std::unique_ptr<Screen> SettingWatchFace::CreateScreen(unsigned int screenNum) c
nScreens,
title,
symbol,
static_cast<uint32_t>(settingsController.GetWatchFace()),
[&settings = settingsController](uint32_t index) {
settings.SetWatchFace(static_cast<WatchFace>(index));
static_cast<uint32_t>(IndexOf(watchfaceItems, settingsController.GetWatchFace())),
[this, &settings = settingsController](uint32_t index) {
settings.SetWatchFace(IndexToWatchFace(watchfaceItems, index));
settings.SaveSettings();
},
watchfacesOnThisScreen);
Expand Down
10 changes: 8 additions & 2 deletions src/displayapp/screens/settings/SettingWatchFace.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Screens::CheckboxList::Item, UserWatchFaceTypes::Count>&& watchfaceItems,
std::array<Item, UserWatchFaceTypes::Count>&& watchfaceItems,
Pinetime::Controllers::Settings& settingsController,
Pinetime::Controllers::FS& filesystem);
~SettingWatchFace() override;
Expand All @@ -33,7 +39,7 @@ namespace Pinetime {
std::unique_ptr<Screen> CreateScreen(unsigned int screenNum) const;

static constexpr int settingsPerScreen = 4;
std::array<Screens::CheckboxList::Item, UserWatchFaceTypes::Count> watchfaceItems;
std::array<Item, UserWatchFaceTypes::Count> watchfaceItems;
static constexpr int nScreens = UserWatchFaceTypes::Count > 0 ? (UserWatchFaceTypes ::Count - 1) / settingsPerScreen + 1 : 1;

Controllers::Settings& settingsController;
Expand Down