diff --git a/.github/workflows/test-EMULATOR.yml b/.github/workflows/test-EMULATOR.yml index 3112d1b66..e16ca8712 100644 --- a/.github/workflows/test-EMULATOR.yml +++ b/.github/workflows/test-EMULATOR.yml @@ -31,10 +31,13 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - name: Update packages - run: sudo apt-get update && sudo apt-get upgrade -y + run: sudo apt-get update - name: Install packages - run: sudo apt-get -y install gcc g++ cmake libsdl2-dev libsdl2-image-dev + run: sudo apt-get -y install gcc g++ cmake libsdl2-dev libsdl2-image-dev python3 python3-pip + - name: Install python packages + run: pip3 install --user -r scripts/requirements.txt - name: Create build directory run: mkdir build - name: CMake ${{ matrix.build-configuration }} diff --git a/.github/workflows/test-FEATURE.yml b/.github/workflows/test-FEATURE.yml index b1016a4bb..d6d96c740 100644 --- a/.github/workflows/test-FEATURE.yml +++ b/.github/workflows/test-FEATURE.yml @@ -28,6 +28,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - id: get-flag run: | echo "feature=$(python3 .github/getWorkflowMatrix.py all_flags)" >> $GITHUB_OUTPUT @@ -56,6 +57,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - name: Cache pip uses: actions/cache@v3 with: diff --git a/.github/workflows/test-OS.yml b/.github/workflows/test-OS.yml index 4d9f15fc7..20b2fbf26 100644 --- a/.github/workflows/test-OS.yml +++ b/.github/workflows/test-OS.yml @@ -15,6 +15,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - uses: dorny/paths-filter@v2.11.1 id: filter with: @@ -49,6 +50,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - name: Cache pip uses: actions/cache@v3 with: diff --git a/.github/workflows/test-OSW.yml b/.github/workflows/test-OSW.yml index d0561c04b..3ba7758fd 100644 --- a/.github/workflows/test-OSW.yml +++ b/.github/workflows/test-OSW.yml @@ -29,6 +29,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - uses: dorny/paths-filter@v2.11.1 id: filter with: @@ -62,6 +63,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.1 with: submodules: recursive + lfs: 'true' - name: Cache pip uses: actions/cache@v3 with: diff --git a/.gitmodules b/.gitmodules index e75d7125b..0347f309b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -38,4 +38,7 @@ [submodule "lib/open-smartwatch-web"] path = lib/open-smartwatch-web url = https://github.com/Open-Smartwatch/open-smartwatch-web.git - branch = dist \ No newline at end of file + branch = dist +[submodule "emulator/lib/ImGUI_TestEngine"] + path = emulator/lib/ImGUI_TestEngine + url = https://github.com/ocornut/imgui_test_engine.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3cc61b45e..8ebb0b3b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.10) +cmake_minimum_required (VERSION 3.12) project (OSW-OS-Emulator) set(CMAKE_CXX_STANDARD 20) @@ -46,8 +46,8 @@ add_library(ImGUI emulator/lib/ImGUI/imgui_draw.cpp emulator/lib/ImGUI/imgui_widgets.cpp emulator/lib/ImGUI/imgui_tables.cpp - emulator/lib/ImGUI/backends/imgui_impl_sdl.cpp - emulator/lib/ImGUI/backends/imgui_impl_sdlrenderer.cpp + emulator/lib/ImGUI/backends/imgui_impl_sdl2.cpp + emulator/lib/ImGUI/backends/imgui_impl_sdlrenderer2.cpp emulator/lib/ImGUI/misc/cpp/imgui_stdlib.cpp ) target_include_directories(ImGUI PUBLIC @@ -60,6 +60,40 @@ target_link_libraries(ImGUI LINK_PUBLIC ${SDL2IMAGE_LIBRARIES} ) +# ImGUI testing engine +add_library(ImGUI_TestEngine + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_engine.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_capture_tool.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_context.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_coroutine.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_exporters.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_perftool.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_ui.cpp + emulator/lib/ImGUI_TestEngine/imgui_test_engine/imgui_te_utils.cpp + +) +target_include_directories(ImGUI_TestEngine PUBLIC + emulator/lib/ImGUI_TestEngine/imgui_test_engine/ +) +target_link_libraries(ImGUI_TestEngine LINK_PUBLIC + ImGUI +) +add_compile_definitions( + IMGUI_ENABLE_TEST_ENGINE=1 + IMGUI_TEST_ENGINE_ENABLE_COROUTINE_STDTHREAD_IMPL=1 +) + +# OSW custom prebuild-scripts +find_package(Python3 REQUIRED COMPONENTS Interpreter) +file(GLOB_RECURSE INCLUDE_OSW_ASSETS ./include/assets/img/** ./include/assets/www/**) +add_custom_target( + osw_script_prebuild_assets ALL + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/build/prebuild_assets.py --output-asset-path ${CMAKE_CURRENT_SOURCE_DIR}/include/assets + BYPRODUCTS ${INCLUDE_OSW_ASSETS} + COMMENT "Generating OSW assets..." +) + # Emulator file(GLOB_RECURSE SOURCES_OSW ./src/*.cpp) file(GLOB_RECURSE SOURCES_OSW_EMULATOR ./emulator/src/*.cpp) @@ -67,6 +101,9 @@ add_executable(emulator.run ${SOURCES_OSW} ${SOURCES_OSW_EMULATOR} ) +add_dependencies(emulator.run + osw_script_prebuild_assets +) target_include_directories(emulator.run PUBLIC ./emulator/include ./include @@ -83,6 +120,7 @@ target_link_libraries(emulator.run LINK_PUBLIC ${SDL2_LIBRARY} ${SDL2IMAGE_LIBRARIES} ImGUI + ImGUI_TestEngine cmdline utest ) @@ -99,8 +137,11 @@ target_compile_definitions(emulator.run PUBLIC $<$: NDEBUG=1 > + # Uncomment the following line to use a different locale (only for the emulator, for the whole OS use the config variable!) + # LOCALE="locales/en-US.h" # Comment these as you wish... OSW_FEATURE_STATS_STEPS + OSW_APPS_EXAMPLES=1 GAME_SNAKE=1 GAME_BRICK_BREAKER=1 TOOL_FLASHLIGHT=1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef6b375d2..d3eed4d20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,21 +1,21 @@ # How to contribute to Open-smartwatch -In case the `develop` branch is ahead of the `master` branch and the `develop` branch is stabile enough, we periodically merge it into the `master`. +In case the `develop` branch is ahead of the `master` branch and the `develop` branch is stable enough, we periodically merge it into the `master`. In order to contribute new or updated documentation, you must first create a GitHub account and fork the original repository to your own account. You can make changes, save them in your repository and then create a pull request against this repository. Unless you are opening a pull request which only makes small corrections (for instance correcting a typo), you are more likely to get traction for your changes if you open an issue first to discuss the proposed changes. -**IMPORTANT** If you want to create a pull request, please work based on the `develop` branch - so we don't have to rebase it... +**IMPORTANT** If you want to create a pull request, please work based on the most recent `develop` branch - so we don't have to rebase it... If you are reading this page, you are possibly interested in contributing to our project 😄 . We have an active (and friendly) developer group and would love to get your help! Some common ways you can support us are: * Testing the code -* Filing issues on GitHub, if you see a problem (or adding detail to existing issues that effect you) +* Reporting issues on GitHub, if you see a problem (or adding detail to existing issues that also affect you) * Fixing issues 😁 * Adding new features -* Reviewing existing pull requests and possibly also contributing to them. -* Translation. Always a good idea... +* Reviewing existing pull requests and possibly also contributing to them +* Translations are always a good idea... ## How to make a good bug report @@ -23,4 +23,4 @@ Submit according to the bug report form. Attach the debug log if necessary. [Rea ## Submitting patches -Please also see our [wiki](https://open-smartwatch.github.io/howto/contribute/) article for that. +Please also see our [wiki](https://open-smartwatch.github.io/howto/contribute/) article for that. \ No newline at end of file diff --git a/README.md b/README.md index 05b9b86cf..3c6e62857 100644 --- a/README.md +++ b/README.md @@ -147,5 +147,28 @@ $ docker run --net=host -e DISPLAY -v /tmp/.X11-unix -d --name OSW -p 22:22 -it $ xauth add <'xauth list' command result> ``` +## Testing +After making some changes to the code, you should test the application by running our unit and UI tests. + +Note: our tests do not cover 100% of the application. If you want to see which parts are covered by tests take a look at `emulator/src/unitTests` and `emulator/src/uiTests`. + +### Unit tests +Run all unit tests: +```bash +$ ./emulator.run --unit_tests +``` +List all unit tests, one per line: +```bash +$ ./emulator.run --list_tests +``` + +### UI tests +Run the emulator with UI tests window: +```bash +$ ./emulator.run --ui_tests +``` + +***IMPORTANT**: If you add some new features, it is strongly recommended to write unit and UI tests for them.* + ## License Everything in this repository is under a GPL-3.0 license, see [here](./LICENSE) for more details. diff --git a/docs/firmware/getting_started.md b/docs/firmware/getting_started.md index 6c409765c..be4573729 100644 --- a/docs/firmware/getting_started.md +++ b/docs/firmware/getting_started.md @@ -2,6 +2,9 @@ This page describes which software you'll need and how to manually flash the firmware. +!!! note "Tip" + In case you won't or can't compile the (latest) firmware yourself, you can download the latest pre-compiled firmware from the [releases page](https://github.com/Open-Smartwatch/open-smartwatch-os/releases) (in case we did not forget to upload it there). In case you want to take a look into what is coming with the next release, checkout the [GitHub Actions](https://github.com/Open-Smartwatch/open-smartwatch-os/actions) (you have to login to download anything). To then flash the firmware to the watch use something like the [ESPHome Flasher](https://github.com/esphome/esphome-flasher), be aware that this flasher will also erase the watches memory every time! An alternative (although not tested) would be the [NodeMCU Flasher](https://github.com/marcelstoer/nodemcu-pyflasher). + ## Required Software - [GIT](https://git-scm.com) @@ -39,7 +42,10 @@ If you have cloned the repo without the recurse option, run `git submodule updat !!! note "Tip" After changing or updating/pulling a branch, run the command again to also update dependencies: - `git submodule update` + ```bash + git pull + git submodule update --init --recursive + ``` Then, open the directory with Visual Studio Code. diff --git a/docs/firmware/troubleshooting.md b/docs/firmware/troubleshooting.md index 31b33a05c..d5f2314f2 100644 --- a/docs/firmware/troubleshooting.md +++ b/docs/firmware/troubleshooting.md @@ -13,8 +13,11 @@ Check the driver insertion. You did not clone the repository with the `--recursive-submodules` flag. !!! note "Tip" - After changing the branch, follow the command : - `git submodule update` + After changing or updating/pulling a branch, run the command again to also update dependencies: + ```bash + git pull + git submodule update --init --recursive + ``` ## Failed to connect to ESP32: Timed out waiting for packet header diff --git a/emulator/include/Emulator.hpp b/emulator/include/Emulator.hpp index deed4c2c8..b9c325910 100644 --- a/emulator/include/Emulator.hpp +++ b/emulator/include/Emulator.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include // for button definitions #include "Jzon.h" @@ -46,8 +48,8 @@ class OswEmulator { void run(); void exit(); - void setButton(unsigned id, bool state); - bool getButton(unsigned id); + void setButton(Button id, bool state); + bool getButton(Button id); uint8_t getBatteryRaw(); bool isCharging(); @@ -67,8 +69,8 @@ class OswEmulator { SDL_Surface* mainSurface = nullptr; // Only used in headless mode SDL_Renderer* mainRenderer = nullptr; std::atomic_bool running = true; - std::array buttons; // TODO This length should come from the platform itself! - std::array buttonCheckboxes = { false, false, false }; // These are just state caches of the buttons for their respective checkboxes! + std::array buttons; + std::array buttonCheckboxes; // These are just state caches of the buttons for their respective checkboxes! bool buttonResetAfterMultiPress = true; uint8_t batRaw = 0; bool charging = true; @@ -100,4 +102,8 @@ class OswEmulator { void doCleanup(); void renderGUIFrameEmulator(); void addGUIHelp(const char* msg); + + // For UI Tests (to access and test private members) + // Note: such friend classes are the only changes in production code related to testing + friend class TestEmulator; }; \ No newline at end of file diff --git a/emulator/include/RtcDS3231.h b/emulator/include/RtcDS3231.h index d146ebb05..988b5c088 100644 --- a/emulator/include/RtcDS3231.h +++ b/emulator/include/RtcDS3231.h @@ -13,7 +13,7 @@ class RtcDateTime { RtcDateTime() {}; virtual ~RtcDateTime() {}; - void InitWithEpoch32Time(time_t time); + void InitWithUnix32Time(time_t time); uint32_t Hour(); uint32_t Minute(); diff --git a/emulator/lib/ImGUI b/emulator/lib/ImGUI index 9e7c0f985..b32ef809c 160000 --- a/emulator/lib/ImGUI +++ b/emulator/lib/ImGUI @@ -1 +1 @@ -Subproject commit 9e7c0f985f9dde58c2b98aa1d6b10dbfb5ce322e +Subproject commit b32ef809c398d490f9c3b42a84d5a5bd744a1b11 diff --git a/emulator/lib/ImGUI_TestEngine b/emulator/lib/ImGUI_TestEngine new file mode 160000 index 000000000..5e06f016e --- /dev/null +++ b/emulator/lib/ImGUI_TestEngine @@ -0,0 +1 @@ +Subproject commit 5e06f016e35793ef09879c4ce81febb10a96f08c diff --git a/emulator/src/Emulator.cpp b/emulator/src/Emulator.cpp index 3a1962f9c..07f8a43fd 100644 --- a/emulator/src/Emulator.cpp +++ b/emulator/src/Emulator.cpp @@ -3,8 +3,8 @@ #include #include "imgui.h" -#include "imgui_impl_sdl.h" -#include "imgui_impl_sdlrenderer.h" +#include "imgui_impl_sdl2.h" +#include "imgui_impl_sdlrenderer2.h" #include "misc/cpp/imgui_stdlib.h" #include "../../include/config_defaults.h" // For the display size @@ -36,6 +36,10 @@ static void shutdownEmulatorByInterruptSignal(int s) { } OswEmulator::OswEmulator(bool headless, std::string configPath, std::string imguiPath): isHeadless(headless) { + // Initialize variables + for(size_t i = 0; i < BTN_NUMBER; i++) + this->buttonCheckboxes[i] = false; + // Load emulator config this->configPath = configPath; this->imguiPath = imguiPath; @@ -81,7 +85,7 @@ OswEmulator::OswEmulator(bool headless, std::string configPath, std::string imgu io.ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos; ImGui::StyleColorsDark(); ImGui_ImplSDL2_InitForSDLRenderer(this->mainWindow, this->mainRenderer); - ImGui_ImplSDLRenderer_Init(this->mainRenderer); + ImGui_ImplSDLRenderer2_Init(this->mainRenderer); } // Install CTRL+C handler in headless mode @@ -99,7 +103,7 @@ OswEmulator::OswEmulator(bool headless, std::string configPath, std::string imgu OswEmulator::~OswEmulator() { if(!this->isHeadless) { // Shutdown ImGUI - ImGui_ImplSDLRenderer_Shutdown(); + ImGui_ImplSDLRenderer2_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); } @@ -144,7 +148,7 @@ void OswEmulator::run() { // Prepare ImGUI for the next frame if(!this->isHeadless) { - ImGui_ImplSDLRenderer_NewFrame(); + ImGui_ImplSDLRenderer2_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); this->renderGUIFrameEmulator(); @@ -256,13 +260,13 @@ void OswEmulator::run() { // Present the fake-display texture as an ImGUI window if(!this->isHeadless) { - ImGui::Begin("Display"); + ImGui::Begin(LANG_IMGUI_DISPLAY "###display"); // Using ImGui::BeginChild() to set the size of the inner window properly ImGui::BeginChild("##FakeDisplayTexture", ImVec2(fakeDisplayInstance->width, fakeDisplayInstance->height)); if(fakeDisplayInstance->isEnabled()) ImGui::Image((void*) fakeDisplayInstance->getTexture(), ImVec2(fakeDisplayInstance->width, fakeDisplayInstance->height)); else - ImGui::Text("Display is not active."); + ImGui::Text(LANG_IMGUI_DISPLAY_NOPE); ImGui::EndChild(); ImGui::End(); } @@ -276,7 +280,7 @@ void OswEmulator::run() { ImGui::Render(); // Draw ImGUI content - ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData()); + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); } // Update the window now with the content of the display @@ -358,12 +362,12 @@ void OswEmulator::requestSleep(RequestSleepState state) { this->requestedSleepState = state; } -void OswEmulator::setButton(unsigned id, bool state) { +void OswEmulator::setButton(Button id, bool state) { this->buttonCheckboxes.at(id) = state; this->buttons.at(id) = state; }; -bool OswEmulator::getButton(unsigned id) { +bool OswEmulator::getButton(Button id) { return this->buttons.at(id); }; @@ -397,48 +401,49 @@ void OswEmulator::addGUIHelp(const char* msg) { void OswEmulator::renderGUIFrameEmulator() { // Emulator control - ImGui::Begin("Emulator"); - ImGui::Text("CPU: %s", this->cpustate == CPUState::active ? "Active" : (this->cpustate == CPUState::light ? "Light Sleep" : "Deep Sleep")); + ImGui::Begin(LANG_IMGUI_EMULATOR "###emulator"); + ImGui::Text("CPU: %s", this->cpustate == CPUState::active ? LANG_EMULATOR_CPU_ACTIVE : (this->cpustate == CPUState::light ? LANG_EMULATOR_CPU_LIGHT_SLEEP : LANG_EMULATOR_CPU_DEEP_SLEEP)); ImGui::PlotLines("FPS Emulator", (float*) this->frameCountsEmulator.data() + 1, this->frameCountsEmulator.size() - 1); ImGui::PlotLines("FPS OSW-UI", (float*) this->frameCountsOsw.data() + 1, this->frameCountsOsw.size() - 1); ImGui::PlotLines("loop()", (float*) this->timesLoop.data(), this->timesLoop.size()); ImGui::Separator(); - ImGui::Checkbox("Keep-Awake", &this->autoWakeUp); - this->addGUIHelp("This will always wakeup the watch for the next frame."); + ImGui::Checkbox(LANG_EMULATOR_WAKELOCK, &this->autoWakeUp); + this->addGUIHelp(LANG_EMULATOR_WAKELOCK_HELP); if(this->cpustate == CPUState::active) { // If false, the ui instance could be unavailable - ImGui::Checkbox("FPS Limiter", &OswUI::getInstance()->mEnableTargetFPS); - this->addGUIHelp("This will limit the FPS to the target FPS set in the OswUI class."); + ImGui::Checkbox(LANG_EMULATOR_FPSLIMIT, &OswUI::getInstance()->mEnableTargetFPS); + this->addGUIHelp(LANG_EMULATOR_FPSLIMIT_HELP); } if(!this->autoWakeUp and this->cpustate != CPUState::active and ImGui::Button("Wake Up")) this->manualWakeUp = true; ImGui::End(); // Button Control - ImGui::Begin("Buttons"); - if(ImGui::Button("Button PWR")) { + ImGui::Begin(LANG_IMGUI_BUTTONS "###buttons"); + if(ImGui::Button(LANG_EMULATOR_BTN " PWR")) { this->enterSleep(true); this->manualWakeUp = true; } - this->addGUIHelp("This button will interrupt the power to the CPU and reset the OS (as from deep sleep)."); + this->addGUIHelp(LANG_EMULATOR_BTN_PWR_HELP); for(size_t buttonId = 0; buttonId < this->buttons.size(); ++buttonId) { - ImGui::Button(("Button " + std::to_string(buttonId + 1)).c_str()); + // Due to this checkbox-alignment they are always one frame behind the button state (but this is not a problem) + ImGui::Checkbox(("##btn" + std::to_string(buttonId + 1)).c_str(), &this->buttonCheckboxes.at(buttonId)); // "##" as prefix hides the label, but still allows for unique ids + ImGui::SameLine(); + ImGui::Button((std::string(LANG_EMULATOR_BTN " ") + ButtonNames[buttonId]).c_str()); if(ImGui::IsItemActivated() or ImGui::IsItemDeactivated()) // Only use the button to control the button state, if it changed during the last frame this->buttonCheckboxes.at(buttonId) = ImGui::IsItemActive(); if(ImGui::IsItemDeactivated() and this->buttonResetAfterMultiPress) { for(size_t bId = 0; bId < this->buttonCheckboxes.size(); ++bId) if(this->buttonCheckboxes.at(bId)) - this->setButton(bId, false); + this->setButton((Button) bId, false); } - ImGui::SameLine(); - ImGui::Checkbox(("##btn" + std::to_string(buttonId + 1)).c_str(), &this->buttonCheckboxes.at(buttonId)); // "##" as prefix hides the label, but still allows for unique ids - this->setButton(buttonId, this->buttonCheckboxes.at(buttonId)); + this->setButton((Button) buttonId, this->buttonCheckboxes.at(buttonId)); } - ImGui::Checkbox("Release after multi-press", &this->buttonResetAfterMultiPress); - this->addGUIHelp("Whenever you press-and-hold any butten(s) by activating their checkbox(es) and then click-and-release any button normally, all other held buttons will also be released."); + ImGui::Checkbox(LANG_EMULATOR_MBTN, &this->buttonResetAfterMultiPress); + this->addGUIHelp(LANG_EMULATOR_MBTN_HELP); ImGui::End(); // Virtual Sensors - ImGui::Begin("Virtual Sensors"); + ImGui::Begin(LANG_IMGUI_VIRTUAL_SENSORS "###virtual_sensors"); if(OswHal::getInstance()->devices() and OswHal::getInstance()->devices()->virtualDevice) { ImGui::InputFloat("Temperature", &OswHal::getInstance()->devices()->virtualDevice->values.temperature, 1, 10); ImGui::InputFloat("Pressure", &OswHal::getInstance()->devices()->virtualDevice->values.pressure, 1, 10); @@ -449,10 +454,10 @@ void OswEmulator::renderGUIFrameEmulator() { ImGui::InputInt("Magnetometer Azimuth", &OswHal::getInstance()->devices()->virtualDevice->values.magnetometerAzimuth, 1, 10); ImGui::InputInt("Steps", (int*) &OswHal::getInstance()->devices()->virtualDevice->values.steps, 1, 10); // Warning - negative values will cause an underflow... ImGui has no conventient way of limiting the input range... } else - ImGui::Text("The virtual sensors are only available, while the virtual device is active."); + ImGui::Text(LANG_IMGUI_VIRTUAL_SENSORS_NOPE); ImGui::End(); - ImGui::Begin("Configuration"); + ImGui::Begin(LANG_IMGUI_CONFIGURATION "###configuration"); if(this->configValuesCache.size()) { for(auto& [label, keyIds] : this->configSectionsToIdCache) { if(ImGui::CollapsingHeader(label.c_str())) @@ -515,7 +520,7 @@ void OswEmulator::renderGUIFrameEmulator() { } } - ImGui::Button("Save"); + ImGui::Button(LANG_SAVE); if(ImGui::IsItemActive()) { OswConfig::getInstance()->enableWrite(); for(size_t keyId = 0; keyId < oswConfigKeysCount; ++keyId) { @@ -553,7 +558,7 @@ void OswEmulator::renderGUIFrameEmulator() { OswConfig::getInstance()->notifyChange(); } } else - ImGui::Text("The configuration is not initialized yet."); + ImGui::Text(LANG_IMGUI_CONFIGURATION_NOPE); ImGui::End(); } diff --git a/emulator/src/IO.cpp b/emulator/src/IO.cpp index 72b59a420..9dac4f491 100644 --- a/emulator/src/IO.cpp +++ b/emulator/src/IO.cpp @@ -4,6 +4,7 @@ #include "Emulator.hpp" #include OSW_TARGET_PLATFORM_HEADER +#include "hal/buttons.h" #include "osw_pins.h" // Used for BTN_* void pinMode(int pin, int mode) { @@ -24,13 +25,13 @@ uint8_t digitalRead(int pin) { const uint8_t buttonClickStates[] = BTN_STATE_ARRAY; switch (pin) { case BTN_1: - return ((OswEmulator::instance->getButton(0) ? LOW : HIGH) == buttonClickStates[0]) ? LOW : HIGH; + return ((OswEmulator::instance->getButton(Button::BUTTON_SELECT) ? LOW : HIGH) == buttonClickStates[Button::BUTTON_SELECT]) ? LOW : HIGH; break; case BTN_2: - return ((OswEmulator::instance->getButton(1) ? LOW : HIGH) == buttonClickStates[1]) ? LOW : HIGH; + return ((OswEmulator::instance->getButton(Button::BUTTON_UP) ? LOW : HIGH) == buttonClickStates[Button::BUTTON_UP]) ? LOW : HIGH; break; case BTN_3: - return ((OswEmulator::instance->getButton(2) ? LOW : HIGH) == buttonClickStates[2]) ? LOW : HIGH; + return ((OswEmulator::instance->getButton(Button::BUTTON_DOWN) ? LOW : HIGH) == buttonClickStates[Button::BUTTON_DOWN]) ? LOW : HIGH; break; case OSW_DEVICE_TPS2115A_STATPWR: return OswEmulator::instance->isCharging() ? 1 : 0; diff --git a/emulator/src/Preferences.cpp b/emulator/src/Preferences.cpp index e74264c34..f06b6bb4e 100644 --- a/emulator/src/Preferences.cpp +++ b/emulator/src/Preferences.cpp @@ -8,6 +8,8 @@ bool Preferences::begin(const char* name, bool readOnly) { // Init "NVS" nvs_flash_init(); this->name = std::string(name); + if(this->name.length() > 15) + return false; // Name too long (max 15 chars, as specified for nvs_open()) // Init Jzon tree by loading (existing) file this->path = std::filesystem::path(preferencesFolderName) / (this->name + ".json"); if(std::filesystem::exists(this->path)) { diff --git a/emulator/src/RtcDS3231.cpp b/emulator/src/RtcDS3231.cpp index 752225110..22470e47b 100644 --- a/emulator/src/RtcDS3231.cpp +++ b/emulator/src/RtcDS3231.cpp @@ -1,6 +1,6 @@ #include "../include/RtcDS3231.h" -void RtcDateTime::InitWithEpoch32Time(time_t time) { +void RtcDateTime::InitWithUnix32Time(time_t time) { this->time = time; } diff --git a/emulator/src/main.cpp b/emulator/src/main.cpp index 78a1180be..97350284c 100644 --- a/emulator/src/main.cpp +++ b/emulator/src/main.cpp @@ -9,6 +9,7 @@ UTEST_STATE(); #include "../include/Emulator.hpp" +#include "tests/uiTests/UiTests_main.hpp" // Global variables, to allow the unit tests to access them int emulatorMainArgc; @@ -21,19 +22,42 @@ int main(int argc, char** argv) { // Parse command line arguments cmdline::parser a; - a.add("unit_tests", '\0', "run the unit test framework"); + const std::string argRunUnitTests = "unit_tests"; + const std::string argListAllTests = "list_tests"; + const std::string argUiTests = "ui_tests"; + a.add(argRunUnitTests, '\0', "run the unit test framework"); + a.add(argListAllTests, '\0', "list all unit and UI tests, one per line"); + a.add(argUiTests, '\0', "run emulator with UI tests window"); a.add("headless", '\0', "do not open a window; use software-rendering only"); // Warning: This parameter name is also used in the unit-tests! a.parse_check(argc, argv); // Initialize SDL - if(SDL_Init(SDL_INIT_EVERYTHING) != 0) + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) printf("error initializing SDL: %s\n", SDL_GetError()); // Run the unit tests or the emulator int returnval = EXIT_SUCCESS; - if (a.exist("unit_tests")) { + if (a.exist(argRunUnitTests)) { // In this mode we won't enter the emulator itself, but instead just run the unit tests returnval = utest_main(argc, argv); + } else if (a.exist(argListAllTests)) { + // Change --list_tests to --list-tests, because utest expects --list-tests + for (int i = 0; i < strlen(argv[1]); i++) { + if (argv[1][i] == '_') { + argv[1][i] = '-'; + break; + } + } + std::cout << "Unit tests:" << std::endl << std::endl; + returnval = utest_main(argc, argv); + std::cout << std::endl << "-------------------" << std::endl << std::endl; + + // returnval here is useless, since UiTests_main always returns 0 + std::cout << "UI tests:" << std::endl << std::endl; + UiTests_main(UiTests_Mode::List); + } else if (a.exist(argUiTests)) { + // Run the emulator together with the testing engine + returnval = UiTests_main(); } else { // Create and run the emulator std::unique_ptr oswEmu = std::make_unique(a.exist("headless")); diff --git a/emulator/src/tests/helpers/TestAlarm.h b/emulator/src/tests/helpers/TestAlarm.h new file mode 100644 index 000000000..82c24e160 --- /dev/null +++ b/emulator/src/tests/helpers/TestAlarm.h @@ -0,0 +1,47 @@ +#pragma once + +#include "../../../include/apps/clock/OswAppAlarm.h" + +// This is a friend class of OswAppAlarm. It is needed to access and test private members of OswAppAlarm +class TestAlarm { + public: + static OswAppAlarm::AlarmState getState(OswAppAlarm& alarm) { + return alarm.state; + } + + static void setState(OswAppAlarm& alarm, OswAppAlarm::AlarmState state) { + alarm.state = state; + } + + static std::array getTimestamp(OswAppAlarm& alarm) { + return alarm.timestamp; + } + + static void setTimestamp(OswAppAlarm& alarm, std::array timestamp) { + alarm.timestamp = timestamp; + } + + static std::array getDaysOfWeek(OswAppAlarm& alarm) { + return alarm.daysOfWeek; + } + + static void setDaysOfWeek(OswAppAlarm& alarm, std::array daysOfWeek) { + alarm.daysOfWeek = daysOfWeek; + } + + static unsigned char getStep(OswAppAlarm& alarm) { + return alarm.step; + } + + static void setStep(OswAppAlarm& alarm, unsigned char step) { + alarm.step = step; + } + + static void reset(OswAppAlarm& alarm) { + alarm.resetAlarmState(); + } + + static std::vector getAlarms(OswAppAlarm& alarm) { + return alarm.notifications; + } +}; \ No newline at end of file diff --git a/emulator/src/tests/helpers/TestAppV2Compat.h b/emulator/src/tests/helpers/TestAppV2Compat.h new file mode 100644 index 000000000..9a4c50412 --- /dev/null +++ b/emulator/src/tests/helpers/TestAppV2Compat.h @@ -0,0 +1,10 @@ +#pragma once + +#include "../../../include/apps/OswAppV2Compat.h" + +class TestAppV2Compat { + public: + static OswApp* getAppV1(OswAppV2Compat& appV2Compat) { + return &appV2Compat.app; + } +}; \ No newline at end of file diff --git a/emulator/src/tests/helpers/TestDrawer.h b/emulator/src/tests/helpers/TestDrawer.h new file mode 100644 index 000000000..78cfa050b --- /dev/null +++ b/emulator/src/tests/helpers/TestDrawer.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "../../../include/apps/OswAppDrawer.h" +#include "../../../include/apps/clock/OswAppTimer.h" +#include "../../../include/apps/clock/OswAppAlarm.h" +#include "TestAppV2Compat.h" +#include "globals.h" +using OswGlobals::main_mainDrawer; + +class TestDrawer { + public: + static OswAppTimer* getTimer() { + // Iterate to the second element (timer) of the TOOLS apps list + auto OswAppTimerIt = std::next((main_mainDrawer.apps.at(LANG_TOOLS).begin())); + TestDrawer::lazyInitTimer = &(*OswAppTimerIt); + + // Cast OswAppV2 to OswAppV2Compat, because we need OswAppV1 app from OswAppV2Compat + OswAppV2Compat& oswAppV2CompatTimer = static_cast(*lazyInitTimer->get()); + + // Get OswAppV1 from OswAppV2Compat + OswAppV1* OswAppV1Timer = TestAppV2Compat::getAppV1(oswAppV2CompatTimer); + + // Cast to timer class + OswAppTimer* timer = dynamic_cast(OswAppV1Timer); + assert(timer); + return timer; + } + static void switchToTimer() { + main_mainDrawer.current = lazyInitTimer; + } + static OswAppAlarm* getAlarm() { + // Iterate to the third element (alarm) of the TOOLS apps list + auto OswAppAlarmIt = std::next(std::next((main_mainDrawer.apps.at(LANG_TOOLS).begin()))); + TestDrawer::lazyInitAlarm = &(*OswAppAlarmIt); + + // Cast OswAppV2 to OswAppV2Compat, because we need OswAppV1 app from OswAppV2Compat + OswAppV2Compat& oswAppV2CompatAlarm = static_cast(*lazyInitAlarm->get()); + + // Get OswAppV1 from OswAppV2Compat + OswAppV1* OswAppV1Alarm = TestAppV2Compat::getAppV1(oswAppV2CompatAlarm); + + // Cast to alarm class + OswAppAlarm* alarm = dynamic_cast(OswAppV1Alarm); + assert(alarm); + return alarm; + } + static void switchToAlarm() { + main_mainDrawer.current = lazyInitAlarm; + } + private: + inline static OswAppDrawer::LazyInit* lazyInitTimer; + inline static OswAppDrawer::LazyInit* lazyInitAlarm; +}; \ No newline at end of file diff --git a/emulator/src/tests/helpers/TestEmulator.h b/emulator/src/tests/helpers/TestEmulator.h new file mode 100644 index 000000000..a603901b0 --- /dev/null +++ b/emulator/src/tests/helpers/TestEmulator.h @@ -0,0 +1,135 @@ +#pragma once + +#include "../../include/Emulator.hpp" +#include "osw_ui.h" +#include "osw_config_keys.h" + +// OswEmulator::instance is a friend class of OswEmulator. It is needed for UI tests of OswEmulator +class TestEmulator { + public: + static void newFrame() { + ImGui_ImplSDLRenderer2_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + } + + static void wakeFromDeepSleep() { + // Revive system after deep sleep as needed + if (OswEmulator::instance->cpustate != OswEmulator::CPUState::active and (OswEmulator::instance->manualWakeUp or OswEmulator::instance->autoWakeUp or (OswEmulator::instance->selfWakeUpAtTimestamp > 0 and OswEmulator::instance->selfWakeUpAtTimestamp < time(nullptr)))) { + if (OswEmulator::instance->manualWakeUp) + OswEmulator::instance->bootReason = OswEmulator::BootReason::byUser; + else if (OswEmulator::instance->autoWakeUp) + OswEmulator::instance->bootReason = OswEmulator::BootReason::byAuto; + else if (OswEmulator::instance->selfWakeUpAtTimestamp > 0 and OswEmulator::instance->selfWakeUpAtTimestamp < time(nullptr)) + OswEmulator::instance->bootReason = OswEmulator::BootReason::byTimer; + else + OswEmulator::instance->bootReason = OswEmulator::BootReason::undefined; // Should never happen... + OswEmulator::instance->cpustate = OswEmulator::CPUState::active; + OswEmulator::instance->manualWakeUp = false; + OswEmulator::instance->selfWakeUpAtTimestamp = 0; + setup(); + + /** + * At the first startup - prepare the key value cache dynamically + * We must execute OswEmulator::instance after the OswConfig::setup() call, as otherwise the key are not initialized yet + */ + if (OswEmulator::instance->configValuesCache.empty()) { + OswEmulator::instance->configValuesCache.resize(oswConfigKeysCount); + for (size_t keyId = 0; keyId < oswConfigKeysCount; ++keyId) { + const OswConfigKey* key = oswConfigKeys[keyId]; + switch (key->type) { + case OswConfigKeyTypedUIType::BOOL: + OswEmulator::instance->configValuesCache[keyId] = dynamic_cast(key)->get(); + break; + case OswConfigKeyTypedUIType::FLOAT: + OswEmulator::instance->configValuesCache[keyId] = dynamic_cast(key)->get(); + break; + case OswConfigKeyTypedUIType::DROPDOWN: + OswEmulator::instance->configValuesCache[keyId] = dynamic_cast(key)->get(); + break; + case OswConfigKeyTypedUIType::SHORT: + OswEmulator::instance->configValuesCache[keyId] = dynamic_cast(key)->get(); + break; + case OswConfigKeyTypedUIType::INT: + OswEmulator::instance->configValuesCache[keyId] = dynamic_cast(key)->get(); + break; + case OswConfigKeyTypedUIType::STRING: + OswEmulator::instance->configValuesCache[keyId] = dynamic_cast(key)->get(); + break; + case OswConfigKeyTypedUIType::RGB: { + const uint32_t color = dynamic_cast(key)->get(); + const std::array rgb = { + rgb888_red(color) / 255.f, + rgb888_green(color) / 255.f, + rgb888_blue(color) / 255.f + }; + OswEmulator::instance->configValuesCache[keyId] = rgb; + } + break; + default: + throw std::runtime_error(std::string("Not implemented key type in cache: ") + (char)key->type); + } + + // Now cache the section label to resolve it later to OswEmulator::instance keys id + std::string labelString = key->section; + if (OswEmulator::instance->configSectionsToIdCache.find(labelString) == OswEmulator::instance->configSectionsToIdCache.end()) + OswEmulator::instance->configSectionsToIdCache.insert({labelString, {}}); + OswEmulator::instance->configSectionsToIdCache.at(labelString).push_back(keyId); + } + } + } + } + + static void drawEmulator() { + // Let the renderer now draw to the FakeDisplays surface + int res = SDL_SetRenderTarget(OswEmulator::instance->mainRenderer, fakeDisplayInstance->getTexture()); + assert(res >= 0 && "Failed to set render target to fake display texture"); + try { + // Run the next OS iteration + std::chrono::time_point start = std::chrono::system_clock::now(); + loop(); + std::chrono::time_point end = std::chrono::system_clock::now(); + for (size_t keyId = 1; keyId < OswEmulator::instance->timesLoop.size(); ++keyId) + OswEmulator::instance->timesLoop.at(OswEmulator::instance->timesLoop.size() - keyId) = OswEmulator::instance->timesLoop.at(OswEmulator::instance->timesLoop.size() - keyId - 1); + OswEmulator::instance->timesLoop.front() = std::chrono::duration_cast(end - start).count(); + // Track the amount of flushing loops per second + if (OswEmulator::instance->lastUiFlush != OswUI::getInstance()->getLastFlush()) { + OswEmulator::instance->lastUiFlush = OswUI::getInstance()->getLastFlush(); + OswEmulator::instance->frameCountsOsw.front()++; + } + } catch (OswEmulator::EmulatorSleep& e) { + // Ignore it :P + } + // And restore emulator surface render target + res = SDL_SetRenderTarget(OswEmulator::instance->mainRenderer, nullptr); // nullptr = back to window surface + assert(res >= 0 && "Failed to set render target to window surface"); + + // Present the fake-display texture as an ImGUI window + if(!OswEmulator::instance->isHeadless) { + ImGui::Begin(LANG_IMGUI_DISPLAY "###display"); + // Using ImGui::BeginChild() to set the size of the inner window properly + ImGui::BeginChild("##FakeDisplayTexture", ImVec2(fakeDisplayInstance->width, fakeDisplayInstance->height)); + if(fakeDisplayInstance->isEnabled()) + ImGui::Image((void*) fakeDisplayInstance->getTexture(), ImVec2(fakeDisplayInstance->width, fakeDisplayInstance->height)); + else + ImGui::Text(LANG_IMGUI_DISPLAY_NOPE); + ImGui::EndChild(); + ImGui::End(); + } + + // If requested try to reset as much as possible + if (OswEmulator::instance->wantCleanup) + OswEmulator::instance->doCleanup(); + } + + static void renderGUIFrameEmulator() { + OswEmulator::instance->renderGUIFrameEmulator(); + } + + static SDL_Renderer* getMainRenderer() { + return OswEmulator::instance->mainRenderer; + } + static SDL_Window* getMainWindow() { + return OswEmulator::instance->mainWindow; + } +}; \ No newline at end of file diff --git a/emulator/src/tests/helpers/TestTimer.h b/emulator/src/tests/helpers/TestTimer.h new file mode 100644 index 000000000..1c356db05 --- /dev/null +++ b/emulator/src/tests/helpers/TestTimer.h @@ -0,0 +1,43 @@ +#pragma once + +#include "../../../include/apps/clock/OswAppTimer.h" + +// This is a friend class of OswAppTimer. It is needed to access and test private members of OswAppTimer +class TestTimer { + public: + static OswAppTimer::TimerState getState(OswAppTimer& timer) { + return timer.state; + } + + static void setState(OswAppTimer& timer, OswAppTimer::TimerState state) { + timer.state = state; + } + + static std::array getTimestamp(OswAppTimer& timer) { + return timer.timestamp; + } + + static void setTimestamp(OswAppTimer& timer, std::array timestamp) { + timer.timestamp = timestamp; + } + + static void timestampToSec(OswAppTimer& timer) { + timer.timestampToSec(); + } + + static std::chrono::seconds getLeftSec(OswAppTimer& timer) { + return timer.timerLeftSec; + } + + static unsigned char getStep(OswAppTimer& timer) { + return timer.step; + } + + static void setStep(OswAppTimer& timer, unsigned char step) { + timer.step = step; + } + + static void reset(OswAppTimer& timer) { + timer.resetTimer(); + } +}; \ No newline at end of file diff --git a/emulator/src/tests/uiTests/OswAppAlarm.cpp b/emulator/src/tests/uiTests/OswAppAlarm.cpp new file mode 100644 index 000000000..215b298f9 --- /dev/null +++ b/emulator/src/tests/uiTests/OswAppAlarm.cpp @@ -0,0 +1,271 @@ +#include + +// ImGUI +#include "imgui.h" + +// Test engine +#include "imgui_te_engine.h" +#include "imgui_te_ui.h" +#include "imgui_te_context.h" + +#include "../../../include/apps/clock/OswAppAlarm.h" +#include "../helpers/TestAlarm.h" +#include "../helpers/TestDrawer.h" + +namespace { +OswAppAlarm* oswAppAlarm; + +void setupTests(ImGuiTestContext* ctx) { + oswAppAlarm = TestDrawer::getAlarm(); + TestDrawer::switchToAlarm(); + + if (TestAlarm::getState(*oswAppAlarm) != OswAppAlarm::AlarmState::IDLE) { + TestAlarm::reset(*oswAppAlarm); + } + + // TODO: remove alarms in a different way + // Deleting alarms this way is tested later, so at this point we don't know if deleting works correctly + while (TestAlarm::getAlarms(*oswAppAlarm).size() > 0) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + } +} +}; + +// Main function with all alarm tests +void RegisterAlarmTests(ImGuiTestEngine* e) { + ImGuiTest* t = NULL; + + // Just open and test the initial state + t = IM_REGISTER_TEST(e, "Alarm", "initial state should be IDLE"); + t->TestFunc = [](ImGuiTestContext *ctx) { + setupTests(ctx); + + const auto currentAlarmState = TestAlarm::getState(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmState, OswAppAlarm::AlarmState::IDLE); + }; + + // Test that BUTTON_2 does not do anything when alarm list is empty + t = IM_REGISTER_TEST(e, "Alarm", "should do nothing when BUTTON_2 is pressed and there are no active alarms"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmState = TestAlarm::getState(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmState, OswAppAlarm::AlarmState::IDLE); + }; + + // Press BUTTON_3 and test the state + t = IM_REGISTER_TEST(e, "Alarm", "should be in time picker state after pressing BUTTON_3"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmState = TestAlarm::getState(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmState, OswAppAlarm::AlarmState::TIME_PICKER); + }; + + // Test decrementing first digit + t = IM_REGISTER_TEST(e, "Alarm", "should decrement first digit correctly ({0}0:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[0], 2); + }; + + // Test incrementing first digit + t = IM_REGISTER_TEST(e, "Alarm", "should increment first digit correctly ({0}0:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[0], 0); + }; + + // Test decrementing second digit + t = IM_REGISTER_TEST(e, "Alarm", "should decrement second digit correctly (0{0}:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[1], 9); + }; + + // Test incrementing second digit + t = IM_REGISTER_TEST(e, "Alarm", "should increment second digit correctly (0{0}:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[1], 0); + }; + + // Test decrementing second digit when the first digit is 2 + t = IM_REGISTER_TEST(e, "Alarm", "should decrement second digit correctly (2{0}:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + + // Set first digit to 2 + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + // Decrement second digit + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[1], 3); + }; + + // Test incrementing second digit when the first digit is 2 + t = IM_REGISTER_TEST(e, "Alarm", "should increment second digit correctly (2{0}:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[1], 0); + + // Set first digit to 0 + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button UP"); + ctx->ItemClick("Button SELECT"); + }; + + // Test decrementing third digit + t = IM_REGISTER_TEST(e, "Alarm", "should decrement third digit correctly (00:{0}0)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[2], 5); + }; + + // Test incrementing third digit + t = IM_REGISTER_TEST(e, "Alarm", "should increment third digit correctly (00:{0}0)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[2], 0); + }; + + // Test decrementing fourth digit + t = IM_REGISTER_TEST(e, "Alarm", "should decrement fourth digit correctly (00:0{0})"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[3], 9); + }; + + // Test incrementing fourth digit + t = IM_REGISTER_TEST(e, "Alarm", "should increment fourth digit correctly (00:0{0})"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmTimestamp = TestAlarm::getTimestamp(*oswAppAlarm); + + IM_CHECK_EQ(currentAlarmTimestamp[3], 0); + }; + + // After pressing "Next" the alarm should be in DAY_PICKER state + t = IM_REGISTER_TEST(e, "Alarm", "should be in day picker state after pressing 'Next'"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmState = TestAlarm::getState(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmState, OswAppAlarm::AlarmState::DAY_PICKER); + }; + + // Test toggling day in DAY_PICKER + t = IM_REGISTER_TEST(e, "Alarm", "should toggle dates correctly"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + auto currentAlarmDays = TestAlarm::getDaysOfWeek(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmDays[0], true); + + ctx->ItemClick("Button UP"); + currentAlarmDays = TestAlarm::getDaysOfWeek(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmDays[0], false); + }; + + // Test state after creating a new alarm + t = IM_REGISTER_TEST(e, "Alarm", "should be in IDLE state after creating a new alarm"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button UP"); + + const auto currentAlarmState = TestAlarm::getState(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmState, OswAppAlarm::AlarmState::IDLE); + }; + + // Test that we created one alarm + t = IM_REGISTER_TEST(e, "Alarm", "should create a new alarm"); + t->TestFunc = [](ImGuiTestContext *ctx) { + const auto currentAlarms = TestAlarm::getAlarms(*oswAppAlarm); + IM_CHECK_EQ(currentAlarms.size(), 1); + }; + + // Test that we can go to LIST state when there is at least one alarm + t = IM_REGISTER_TEST(e, "Alarm", "should show delete options when BUTTON_2 is pressed and there are active alarms"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarmState = TestAlarm::getState(*oswAppAlarm); + IM_CHECK_EQ(currentAlarmState, OswAppAlarm::AlarmState::LIST); + }; + + // Test that we can delete the alarm + t = IM_REGISTER_TEST(e, "Alarm", "should be able to delete the alarm"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + + const auto currentAlarms = TestAlarm::getAlarms(*oswAppAlarm); + IM_CHECK_EQ(currentAlarms.size(), 0); + }; +} \ No newline at end of file diff --git a/emulator/src/tests/uiTests/OswAppTimer.cpp b/emulator/src/tests/uiTests/OswAppTimer.cpp new file mode 100644 index 000000000..a084422d7 --- /dev/null +++ b/emulator/src/tests/uiTests/OswAppTimer.cpp @@ -0,0 +1,237 @@ +#include + +// ImGUI +#include "imgui.h" + +// Test engine +#include "imgui_te_engine.h" +#include "imgui_te_ui.h" +#include "imgui_te_context.h" + +#include "../../../include/apps/clock/OswAppTimer.h" +#include "../helpers/TestTimer.h" +#include "../helpers/TestDrawer.h" + +namespace { +OswAppTimer* oswAppTimer; + +void setupTests() { + oswAppTimer = TestDrawer::getTimer(); + TestDrawer::switchToTimer(); + + if (TestTimer::getState(*oswAppTimer) != OswAppTimer::TimerState::IDLE) { + TestTimer::reset(*oswAppTimer); + } +} +}; + +// Main function with all timer tests +void RegisterTimerTests(ImGuiTestEngine* e) { + ImGuiTest* t = NULL; + + // Just open and test the initial state + t = IM_REGISTER_TEST(e, "Timer", "initial state should be IDLE"); + t->TestFunc = [](ImGuiTestContext *ctx) { + setupTests(); + + const auto currentTimerState = TestTimer::getState(*oswAppTimer); + IM_CHECK_EQ(currentTimerState, OswAppTimer::TimerState::IDLE); + }; + + // Press BUTTON_3 and test the state + t = IM_REGISTER_TEST(e, "Timer", "should be in SET_TIMER_SCREEN state after pressing BUTTON_3"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimerState = TestTimer::getState(*oswAppTimer); + std::cout << (int)currentTimerState << std::endl; + IM_CHECK_EQ(currentTimerState, OswAppTimer::TimerState::SET_TIMER_SCREEN); + }; + + // Press BUTTON_1 to move to the one digit right + t = IM_REGISTER_TEST(e, "Timer", "after pressing BUTTON_1 should move to the next digit"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + + const auto currentDigit = TestTimer::getStep(*oswAppTimer); + IM_CHECK_EQ(currentDigit, 1); + + // Return to the first digit to continue testing + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + }; + + // Decrement the first digit + t = IM_REGISTER_TEST(e, "Timer", "should decrement first digit correctly ({0}0:00:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[0], 9); + }; + + // Increment the first digit + t = IM_REGISTER_TEST(e, "Timer", "should increment first digit correctly ({0}0:00:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[0], 0); + }; + + // Decrement the second digit + t = IM_REGISTER_TEST(e, "Timer", "should decrement second digit correctly (0{0}:00:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[1], 9); + }; + + // Increment the second digit + t = IM_REGISTER_TEST(e, "Timer", "should increment second digit correctly (0{0}:00:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[1], 0); + }; + + // Decrement the third digit + t = IM_REGISTER_TEST(e, "Timer", "should decrement third digit correctly (00:{0}0:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[2], 5); + }; + + // Increment the third digit + t = IM_REGISTER_TEST(e, "Timer", "should increment third digit correctly (00:{0}0:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[2], 0); + }; + + // Decrement the fourth digit + t = IM_REGISTER_TEST(e, "Timer", "should decrement fourth digit correctly (00:0{0}:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[3], 9); + }; + + // Increment the fourth digit + t = IM_REGISTER_TEST(e, "Timer", "should increment fourth digit correctly (00:0{0}:00)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[3], 0); + }; + + // Decrement the fifth digit + t = IM_REGISTER_TEST(e, "Timer", "should decrement fifth digit correctly (00:00:{0}0)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[4], 5); + }; + + // Increment the fifth digit + t = IM_REGISTER_TEST(e, "Timer", "should increment fifth digit correctly (00:00:{0}0)"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[4], 0); + }; + + // Decrement the sixth digit + t = IM_REGISTER_TEST(e, "Timer", "should decrement sixth digit correctly (00:00:0{0})"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button DOWN"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[5], 9); + }; + + // Increment the sixth digit + t = IM_REGISTER_TEST(e, "Timer", "should increment sixth digit correctly (00:00:0{0})"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimestamp = TestTimer::getTimestamp(*oswAppTimer); + IM_CHECK_EQ(currentTimestamp[5], 0); + }; + + // Start the timer and test the state + t = IM_REGISTER_TEST(e, "Timer", "should be in RUNNING state after starting"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button DOWN"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button SELECT"); + ctx->ItemClick("Button UP"); + + const auto currentTimerState = TestTimer::getState(*oswAppTimer); + IM_CHECK_EQ(currentTimerState, OswAppTimer::TimerState::RUNNING); + }; + + // Pause the timer and test the state + t = IM_REGISTER_TEST(e, "Timer", "should be in PAUSED state after pausing"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->ItemClick("Button UP"); + + const auto currentTimerState = TestTimer::getState(*oswAppTimer); + IM_CHECK_EQ(currentTimerState, OswAppTimer::TimerState::PAUSED); + + // Resume the timer after test + ctx->ItemClick("Button UP"); + }; + + // Start the timer and test the reset + t = IM_REGISTER_TEST(e, "Timer", "should reset correctly"); + t->TestFunc = [](ImGuiTestContext *ctx) { + ctx->SetRef("###buttons"); + ctx->MouseMove("Button DOWN"); // Move to the reset button + ctx->MouseDown(0); + ctx->SleepNoSkip(3.0f, 0.01f); + ctx->MouseUp(0); + + const auto currentTimerState = TestTimer::getState(*oswAppTimer); + IM_CHECK_EQ(currentTimerState, OswAppTimer::TimerState::IDLE); + + const auto timerLeftSec = TestTimer::getLeftSec(*oswAppTimer); + IM_CHECK_EQ(timerLeftSec, std::chrono::seconds{0}); + }; +} \ No newline at end of file diff --git a/emulator/src/tests/uiTests/RegisterUiTests.h b/emulator/src/tests/uiTests/RegisterUiTests.h new file mode 100644 index 000000000..500bb72d5 --- /dev/null +++ b/emulator/src/tests/uiTests/RegisterUiTests.h @@ -0,0 +1,17 @@ +#pragma once + +#include "imgui_te_engine.h" +#include +#include + +// Declaration of UI tests +// IMPORTANT: If you add new UI test, add it's declaration here +void RegisterTimerTests(ImGuiTestEngine* e); +void RegisterAlarmTests(ImGuiTestEngine* e); + +// Array of all UI tests. These tests will be displayed in the testing engine +// IMPORTANT: If you added UI test declaration above, then you should also pointer to your function to this array +const std::array RegisterUiTests{ + &RegisterTimerTests, + &RegisterAlarmTests +}; \ No newline at end of file diff --git a/emulator/src/tests/uiTests/UiTests_main.hpp b/emulator/src/tests/uiTests/UiTests_main.hpp new file mode 100644 index 000000000..85311956e --- /dev/null +++ b/emulator/src/tests/uiTests/UiTests_main.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// ImGUI +#define IMGUI_DEFINE_MATH_OPERATORS +#include "imgui.h" +#include "imgui_internal.h" +#include "imgui_impl_sdl2.h" +#include "imgui_impl_sdlrenderer2.h" + +// Test engine +#include "imgui_te_engine.h" +#include "imgui_te_ui.h" +#include "imgui_te_context.h" +#include "imgui_te_exporters.h" +#include "imgui_te_internal.h" + +#include "../../include/Emulator.hpp" +#include "../helpers/TestEmulator.h" +#include "RegisterUiTests.h" + +// There is one more possible mode - RunHeadless, but it is not implemented yet +enum class UiTests_Mode { + Run, + List +}; + +int UiTests_main(UiTests_Mode mode = UiTests_Mode::Run) { + // Initialize SDL + if (SDL_Init(SDL_INIT_EVERYTHING) != 0) + printf("error initializing SDL: %s\n", SDL_GetError()); + + const bool isListMode = mode == UiTests_Mode::List; + // Create and run the emulator + std::unique_ptr oswEmu = std::make_unique(isListMode); + OswEmulator::instance = oswEmu.get(); + + // Setup test engine + ImGuiTestEngine* engine = ImGuiTestEngine_CreateContext(); + ImGuiTestEngineIO& test_io = ImGuiTestEngine_GetIO(engine); + test_io.ConfigVerboseLevel = ImGuiTestVerboseLevel_Info; + test_io.ConfigVerboseLevelOnError = ImGuiTestVerboseLevel_Debug; + test_io.ConfigRunSpeed = ImGuiTestRunSpeed_Fast; // Default to fastest mode + + // Register tests + std::for_each(RegisterUiTests.begin(), RegisterUiTests.end(), [engine](auto RegisterTest) { + RegisterTest(engine); + }); + + if (isListMode) { + for (auto uiTest : engine->TestsAll) { + std::cout << uiTest->Category << ": " << uiTest->Name << std::endl; + } + return 0; + } + + // Start test engine + ImGuiTestEngine_Start(engine, ImGui::GetCurrentContext()); + ImGuiTestEngine_InstallDefaultCrashHandler(); + + // Main loop + bool aborted = false; + + // Skip tutorial + TestEmulator::newFrame(); + TestEmulator::renderGUIFrameEmulator(); + TestEmulator::wakeFromDeepSleep(); + TestEmulator::drawEmulator(); + ImGui::Render(); + OswUI::getInstance()->setRootApplication(&OswGlobals::main_mainDrawer); + + while (!aborted) { + // Handle exit event + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) { + aborted = true; + break; + } + } + + SDL_RenderClear(TestEmulator::getMainRenderer()); + + // Emulator new frame + TestEmulator::newFrame(); + + TestEmulator::renderGUIFrameEmulator(); + + TestEmulator::wakeFromDeepSleep(); + + TestEmulator::drawEmulator(); + + // Show "ImGUI Test Engine" window + ImGuiTestEngine_ShowTestEngineWindows(engine, NULL); + + // Render + ImGui::Render(); + + // Draw ImGUI content + ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData()); + + // Update the window now with the content of the display + SDL_RenderPresent(TestEmulator::getMainRenderer()); + SDL_UpdateWindowSurface(TestEmulator::getMainWindow()); + + // Post-swap handler is REQUIRED in order to support screen capture + ImGuiTestEngine_PostSwap(engine); + } + + // Shutdown + ImGuiTestEngine_Stop(engine); + + oswEmu.reset(); + + // IMPORTANT: we need to destroy the Dear ImGui context BEFORE the test engine context, so .ini data may be saved. + ImGuiTestEngine_DestroyContext(engine); + + OswEmulator::instance = nullptr; + + SDL_Quit(); + + return 0; +} \ No newline at end of file diff --git a/emulator/src/tests/OswAppAlarm.cpp b/emulator/src/tests/unitTests/OswAppAlarm.cpp similarity index 94% rename from emulator/src/tests/OswAppAlarm.cpp rename to emulator/src/tests/unitTests/OswAppAlarm.cpp index 8648015bd..24b3dcf3c 100644 --- a/emulator/src/tests/OswAppAlarm.cpp +++ b/emulator/src/tests/unitTests/OswAppAlarm.cpp @@ -44,8 +44,7 @@ class TestAlarm { // Helpers static OswAppAlarm createAlarm() { - std::shared_ptr mockSwitcher = std::make_shared(BUTTON_1, SHORT_PRESS, false, false, nullptr); - OswAppAlarm alarm{mockSwitcher.get()}; + OswAppAlarm alarm; return alarm; } diff --git a/emulator/src/tests/OswAppTimer.cpp b/emulator/src/tests/unitTests/OswAppTimer.cpp similarity index 95% rename from emulator/src/tests/OswAppTimer.cpp rename to emulator/src/tests/unitTests/OswAppTimer.cpp index ab0aadfdd..522dad23b 100644 --- a/emulator/src/tests/OswAppTimer.cpp +++ b/emulator/src/tests/unitTests/OswAppTimer.cpp @@ -44,8 +44,7 @@ class TestTimer { // Helpers static OswAppTimer createTimer() { - std::shared_ptr mockSwitcher = std::make_shared(BUTTON_1, SHORT_PRESS, false, false, nullptr); - OswAppTimer timer{mockSwitcher.get()}; + OswAppTimer timer; return timer; } diff --git a/emulator/src/tests/OswServiceTaskNotifier.cpp b/emulator/src/tests/unitTests/OswServiceTaskNotifier.cpp similarity index 100% rename from emulator/src/tests/OswServiceTaskNotifier.cpp rename to emulator/src/tests/unitTests/OswServiceTaskNotifier.cpp diff --git a/emulator/src/tests/emulator.cpp b/emulator/src/tests/unitTests/emulator.cpp similarity index 100% rename from emulator/src/tests/emulator.cpp rename to emulator/src/tests/unitTests/emulator.cpp diff --git a/emulator/src/tests/fixtures/CaptureSerialFixture.hpp b/emulator/src/tests/unitTests/fixtures/CaptureSerialFixture.hpp similarity index 100% rename from emulator/src/tests/fixtures/CaptureSerialFixture.hpp rename to emulator/src/tests/unitTests/fixtures/CaptureSerialFixture.hpp diff --git a/emulator/src/tests/fixtures/EmulatorFixture.hpp b/emulator/src/tests/unitTests/fixtures/EmulatorFixture.hpp similarity index 100% rename from emulator/src/tests/fixtures/EmulatorFixture.hpp rename to emulator/src/tests/unitTests/fixtures/EmulatorFixture.hpp diff --git a/emulator/src/tests/fixtures/PreferencesFixture.hpp b/emulator/src/tests/unitTests/fixtures/PreferencesFixture.hpp similarity index 100% rename from emulator/src/tests/fixtures/PreferencesFixture.hpp rename to emulator/src/tests/unitTests/fixtures/PreferencesFixture.hpp diff --git a/emulator/src/tests/logging.cpp b/emulator/src/tests/unitTests/logging.cpp similarity index 100% rename from emulator/src/tests/logging.cpp rename to emulator/src/tests/unitTests/logging.cpp diff --git a/emulator/src/tests/serial.cpp b/emulator/src/tests/unitTests/serial.cpp similarity index 100% rename from emulator/src/tests/serial.cpp rename to emulator/src/tests/unitTests/serial.cpp diff --git a/emulator/src/tests/string.cpp b/emulator/src/tests/unitTests/string.cpp similarity index 100% rename from emulator/src/tests/string.cpp rename to emulator/src/tests/unitTests/string.cpp diff --git a/img/icons/.gitignore b/img/icons/.gitignore new file mode 100644 index 000000000..e8f6ad08c --- /dev/null +++ b/img/icons/.gitignore @@ -0,0 +1 @@ +*.xcf \ No newline at end of file diff --git a/img/icons/alarm.png b/img/icons/alarm.png new file mode 100644 index 000000000..040eaa1be --- /dev/null +++ b/img/icons/alarm.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:443f24dec250a880139361df254f22759bf175198ad7a65f4910d61cebef8509 +size 5345 diff --git a/img/icons/app.png b/img/icons/app.png new file mode 100644 index 000000000..a73d3d0bc --- /dev/null +++ b/img/icons/app.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e4ef17f88765c16b1924a3660a859b7d85264d01988e587d1877ed40aacefe8 +size 634 diff --git a/img/icons/brickbreaker.png b/img/icons/brickbreaker.png new file mode 100644 index 000000000..a7311a52d --- /dev/null +++ b/img/icons/brickbreaker.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab1751aeea6004a7981dfc334dbd64a60f5ba32b52f99906a45301a56216cc54 +size 5094 diff --git a/img/icons/calculator.png b/img/icons/calculator.png new file mode 100644 index 000000000..3029b99b3 --- /dev/null +++ b/img/icons/calculator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cebad84e52855d53a4ee7e9426610f1e88276ba564c840b177e2d00681a89f22 +size 4919 diff --git a/img/icons/check.png b/img/icons/check.png new file mode 100644 index 000000000..71026c2b2 --- /dev/null +++ b/img/icons/check.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c50478c8879e9051d0761e477cf150a4587ad1e5bbf4851f98cb7e34aff4c65 +size 600 diff --git a/img/icons/flashlight.png b/img/icons/flashlight.png new file mode 100644 index 000000000..1885f33b3 --- /dev/null +++ b/img/icons/flashlight.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c886fc974b0af4aec4f5a5ede24f03183d31da77ed026c0ac6c4e3865643927 +size 4844 diff --git a/img/icons/osw.png b/img/icons/osw.png new file mode 100644 index 000000000..a45419ad9 --- /dev/null +++ b/img/icons/osw.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:028cfeac6dfea46e9c662bda752dcbf2b98e7ad9dfeb6e19bea7a93c450a4eee +size 705 diff --git a/img/icons/settings.png b/img/icons/settings.png new file mode 100644 index 000000000..cabf434dc --- /dev/null +++ b/img/icons/settings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ea7afdafaad517fe6f4a5cb77ff28cf513cd31083407aa572e92d53ef2ab82e +size 5648 diff --git a/img/icons/snake.png b/img/icons/snake.png new file mode 100644 index 000000000..04dbcfa35 --- /dev/null +++ b/img/icons/snake.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99edd29fe2ebc041cb367d9f69dc353bf471c7f2f0a245e49749bd1e6b9370d3 +size 4862 diff --git a/img/icons/stopwatch.png b/img/icons/stopwatch.png new file mode 100644 index 000000000..57f9857ef --- /dev/null +++ b/img/icons/stopwatch.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:774a2498cce0c63a0161ccb4f4bf309ffc6320c925e406941cb7f16da7d23e48 +size 5118 diff --git a/img/icons/time.png b/img/icons/time.png new file mode 100644 index 000000000..0e0e40f33 --- /dev/null +++ b/img/icons/time.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2195eb68d047e3f2b1a0836604af5635200fa7a129a788a9c9d3652f14f49943 +size 5827 diff --git a/img/icons/timer.png b/img/icons/timer.png new file mode 100644 index 000000000..1f7429cec --- /dev/null +++ b/img/icons/timer.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e59b81aca24d084d1c9b571ebd64f809c4d8cba83878096f00d9e53d210f0cc +size 5354 diff --git a/img/icons/wait.png b/img/icons/wait.png new file mode 100644 index 000000000..b4407f604 --- /dev/null +++ b/img/icons/wait.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd39b858defe6a4bdb7c5a52ab9c9f0b591c2008922824a313323809d500f67d +size 600 diff --git a/img/icons/warning.png b/img/icons/warning.png new file mode 100644 index 000000000..d82c24649 --- /dev/null +++ b/img/icons/warning.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6126c1b932e16b622a82c64ca0df7b518e384a3a52bac9e297a2058edaff2c3 +size 604 diff --git a/img/icons/waterlevel.png b/img/icons/waterlevel.png new file mode 100644 index 000000000..0fd00d9ca --- /dev/null +++ b/img/icons/waterlevel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4428ba5ffab2a65c1b7a284ffd16cf435b84d00242af1253bb5377bec3ada96 +size 4917 diff --git a/img/static/example.png b/img/static/example.png new file mode 100644 index 000000000..cdbb52aba --- /dev/null +++ b/img/static/example.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42063150e44c9fbb98434ab5b026d9f931f7cc9bb9e82e53467290545d134e0c +size 26440 diff --git a/include/osw_app.h b/include/OswAppV1.h similarity index 76% rename from include/osw_app.h rename to include/OswAppV1.h index 924a72bb0..01d076ec2 100644 --- a/include/osw_app.h +++ b/include/OswAppV1.h @@ -1,13 +1,11 @@ -#ifndef OSW_APP_H -#define OSW_APP_H - +#pragma once #include #ifdef OSW_EMULATOR #include "imgui.h" #endif -class OswApp { +class OswAppV1 { public: virtual void setup() = 0; virtual void loop() = 0; @@ -17,4 +15,4 @@ class OswApp { #endif }; -#endif \ No newline at end of file +typedef OswAppV1 OswApp; // For backwards compatibility \ No newline at end of file diff --git a/include/OswAppV2.h b/include/OswAppV2.h new file mode 100644 index 000000000..744086f44 --- /dev/null +++ b/include/OswAppV2.h @@ -0,0 +1,82 @@ +#pragma once +#include + +#include +#include // used for own default app icon +#include +#include + +class OswHal; +class OswAppV2 { + public: + enum ViewFlags: char { + NONE = 0, + NO_OVERLAYS = 1, + KEEP_DISPLAY_ON = 2, + NO_FPS_LIMIT = 4 + }; + enum ButtonStateNames: char { + UNDEFINED = 0, + SHORT_PRESS = 1, + LONG_PRESS = 2, + VERY_LONG_PRESS = 4, + DOUBLE_PRESS = 8 + }; + + OswAppV2(); + virtual ~OswAppV2() = default; + + virtual const char* getAppId() = 0; + virtual const char* getAppName() = 0; + virtual const OswIcon& getAppIcon(); + + virtual void onStart(); + virtual void onLoop(); + virtual void onDraw(); + virtual void onDrawOverlay(); + virtual void onStop(); + + virtual void onButton(Button id, bool up, ButtonStateNames state); +#ifdef OSW_EMULATOR + virtual void onLoopDebug(); // By default no debug loop (GUI) is implemented +#endif + + virtual const ViewFlags& getViewFlags(); + virtual bool getNeedsRedraw(); + virtual void resetNeedsRedraw(); + protected: + class OswHalProxy { + public: + // This will proxy the "->" operator to use the current instance of OswHal + OswHal* operator->() { + return OswHal::getInstance(); + }; + operator OswHal* () { + return OswHal::getInstance(); + }; + // We intentionally do not provide an operation to implicitly convert to OswHal* to prevent accidental use of the wrong instance + }; + OswHalProxy hal; // You guys are needing that anyways (but you often cache incorrectly), so it is now given to you <3 + class OswUiProxy { + public: + OswUI* operator->() { + return OswUI::getInstance(); + }; + operator OswUI* () { + return OswUI::getInstance(); + }; + }; + OswUiProxy ui; + std::array knownButtonStates; // Bitmask of known button states, use this to ignore unhandled button states + ViewFlags viewFlags = ViewFlags::NONE; + bool needsRedraw = false; + const OswIcon& getDefaultAppIcon(); + void clearKnownButtonStates(); + + private: + static OswIconProgmem defaultAppIcon; + std::array buttonDownSince = {0}; + std::array buttonLastSentState = {ButtonStateNames::UNDEFINED}; + std::array buttonDoubleShortTimeout = {0}; + std::array buttonIndicatorProgress = {0}; +}; \ No newline at end of file diff --git a/include/OswIcon.h b/include/OswIcon.h new file mode 100644 index 000000000..5a1e1f522 --- /dev/null +++ b/include/OswIcon.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include + +class OswIcon { + public: + static const unsigned int baseDimensions = 16; // every icon (regardless of source size) shall be treated as a 16x16 grid + uint16_t color; + + OswIcon(uint16_t color): color(color) {}; + + virtual void draw(Graphics2D* gfx, int x, int y, float scale = 1, OswImage::Alignment xAlign = OswImage::Alignment::START, OswImage::Alignment yAlign = OswImage::Alignment::START) const = 0; +}; \ No newline at end of file diff --git a/include/OswImage.h b/include/OswImage.h new file mode 100644 index 000000000..057a5e7e2 --- /dev/null +++ b/include/OswImage.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class OswImage { + public: + enum class Alignment { + START, + CENTER, + END + }; + + OswImage(const unsigned char* data, unsigned int length, unsigned short width, unsigned short height); + + void draw(Graphics2D* gfx, int x, int y, float scale = 1, Alignment xAlign = Alignment::START, Alignment yAlign = Alignment::START); + private: + static Graphics2D* cbGfx; + static unsigned int cbOffX; + static unsigned int cbOffY; + static Alignment cbAlignX; + static Alignment cbAlignY; + static float cbScale; + const unsigned char* data; + const unsigned int length; + const unsigned short width; + const unsigned short height; + + static void drawCallback(pngle_t* pngle, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned char rgba[4]); +}; \ No newline at end of file diff --git a/include/apps/OswAppDrawer.h b/include/apps/OswAppDrawer.h new file mode 100644 index 000000000..9c7496b82 --- /dev/null +++ b/include/apps/OswAppDrawer.h @@ -0,0 +1,109 @@ +#pragma once +#include +#include +#include +#include + +#include +#include + +class OswAppDrawer: public OswAppV2 { + public: + static const size_t UNDEFINED_SLEEP_APP_INDEX = (size_t) -1; // just use a very large number + + OswAppDrawer(size_t* sleepPersistantAppIndex = nullptr); + + const char* getAppId() override; + const char* getAppName() override; + + void onStart() override; + void onLoop() override; + void onDraw() override; + void onDrawOverlay() override; + void onStop() override; + + void onButton(Button id, bool up, ButtonStateNames state) override; +#ifdef OSW_EMULATOR + void onLoopDebug() override; +#endif + + const ViewFlags& getViewFlags() override; + bool getNeedsRedraw() override; + void resetNeedsRedraw() override; + + void registerApp(const char* category, OswAppV2* app); + template + void registerAppLazy(const char* category) { + if(!this->apps.count(category)) + this->apps.emplace(std::make_pair(category, std::move(std::list()))); + this->apps.at(category).emplace_back(nullptr); + this->apps.at(category).back().set(); + }; + + // Control functions, those will schedule their action for the next loop() of the drawer (preventing e.g. undefined behavior of switching apps while drawing) + void showDrawer(); + void startApp(const char* appId); + private: + class LazyInit { + public: + LazyInit(OswAppV2* given) { + this->ptr = given; + }; + LazyInit(LazyInit& other) = delete; // prevent copying, which may causes issues with the cleanup + + template + void set() { + this->init = []() -> OswAppV2* { + return new T(); + }; + } + + OswAppV2* get() { + if(this->ptr == nullptr) + this->ptr = this->init(); + return this->ptr; + } + + bool operator==(const LazyInit& other) const { + // either the app-instance is the same, or the initialization function is the same + return (this->ptr != nullptr and other.ptr != nullptr and this->ptr == other.ptr) or (this->init != nullptr and other.init != nullptr and this->init == other.init); + } + + bool operator!=(const LazyInit& other) const { + return not (*this == other); + } + + void cleanup() { + if(this->init != nullptr) { + delete this->ptr; + this->ptr = nullptr; + } + } + + virtual ~LazyInit() { + this->cleanup(); + } + private: + OswAppV2* (*init)() = nullptr; + OswAppV2* ptr = nullptr; + }; + static bool minimizeButtonLabels; // if you know one drawer, you know them all ;) + std::map> apps; // using string view for correct ordering & comparison + LazyInit* current = nullptr; + size_t highlightCategoryIndex = 0; + size_t highlightAppIndex = 0; + size_t categoryIndexOffset = 0; + LazyInit* highlightApp = nullptr; + size_t* sleepPersistantAppIndex; + + OswAppDrawer::LazyInit* nextLoopAppOpen = nullptr; + bool nextLoopDrawerOpen = false; + + void cleanup(); + void drawer(); + void open(LazyInit& app); + + // For UI testing purposes (to access private member "apps") + // IMPORTANT: declaring such friend classes are the only changes in production code for testing purposes + friend class TestDrawer; +}; \ No newline at end of file diff --git a/include/apps/OswAppV2Compat.h b/include/apps/OswAppV2Compat.h new file mode 100644 index 000000000..ea6b8736d --- /dev/null +++ b/include/apps/OswAppV2Compat.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include + +#include +#include + +class OswAppV2Compat: public OswAppV2 { + public: + OswAppV2Compat(const char* id, const char* name, OswAppV1& app, bool keepScreenOn = true, std::optional> icon = std::nullopt); + + const char* getAppId() override; + const char* getAppName() override; + const OswIcon& getAppIcon() override; + + void onStart() override; + void onLoop() override; + void onDraw() override; + void onDrawOverlay() override; + void onStop() override; + +#ifdef OSW_EMULATOR + void onLoopDebug() override; +#endif + + private: + const char* id; + const char* name; + const OswIcon& icon; + OswAppV1& app; + bool keepScreenOn; + + // For UI testing purposes (to access private member "app") + friend class TestAppV2Compat; +}; \ No newline at end of file diff --git a/include/apps/_experiments/OswAppWeather.h b/include/apps/_experiments/OswAppWeather.h index 4cae2cc4a..326885b82 100644 --- a/include/apps/_experiments/OswAppWeather.h +++ b/include/apps/_experiments/OswAppWeather.h @@ -2,7 +2,7 @@ #ifdef OSW_FEATURE_WEATHER #include #include -#include "osw_app.h" +#include #include "OswAppWeatherIconPrinter.h" class OswAppWeather : public OswApp { diff --git a/include/apps/_experiments/autumn.h b/include/apps/_experiments/autumn.h index eae345639..5050be2a6 100644 --- a/include/apps/_experiments/autumn.h +++ b/include/apps/_experiments/autumn.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppAutumn : public OswApp { public: diff --git a/include/apps/_experiments/dnatilt.h b/include/apps/_experiments/dnatilt.h index 81d835be6..7c802621c 100644 --- a/include/apps/_experiments/dnatilt.h +++ b/include/apps/_experiments/dnatilt.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppDNATilt : public OswApp { public: diff --git a/include/apps/_experiments/fadein_display.h b/include/apps/_experiments/fadein_display.h index 57081d561..981a6401b 100644 --- a/include/apps/_experiments/fadein_display.h +++ b/include/apps/_experiments/fadein_display.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppFadeInDisplay : public OswApp { public: diff --git a/include/apps/_experiments/fireworks.h b/include/apps/_experiments/fireworks.h index ebee0b01f..3f253b0c0 100644 --- a/include/apps/_experiments/fireworks.h +++ b/include/apps/_experiments/fireworks.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppFireworks : public OswApp { public: diff --git a/include/apps/_experiments/gif_player.h b/include/apps/_experiments/gif_player.h index 6d181ec55..11c1b701c 100644 --- a/include/apps/_experiments/gif_player.h +++ b/include/apps/_experiments/gif_player.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppGifPlayer : public OswApp { public: diff --git a/include/apps/_experiments/hello_world.h b/include/apps/_experiments/hello_world.h deleted file mode 100644 index cff70cc53..000000000 --- a/include/apps/_experiments/hello_world.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef OSW_APP_HELLO_WORLD_H -#define OSW_APP_HELLO_WORLD_H - -#include - -#include "osw_app.h" - -class OswAppHelloWorld : public OswApp { - public: - OswAppHelloWorld(void) {}; - virtual void setup() override; - virtual void loop() override; - virtual void stop() override; - ~OswAppHelloWorld() {}; - - private: -}; - -#endif diff --git a/include/apps/_experiments/magnetometer_calibrate.h b/include/apps/_experiments/magnetometer_calibrate.h index e76ac011c..e6b818762 100644 --- a/include/apps/_experiments/magnetometer_calibrate.h +++ b/include/apps/_experiments/magnetometer_calibrate.h @@ -1,7 +1,7 @@ #pragma once #include -#include "osw_app.h" +#include #if OSW_PLATFORM_ENVIRONMENT_MAGNETOMETER == 1 && OSW_PLATFORM_HARDWARE_QMC5883L == 1 class OswAppMagnetometerCalibrate : public OswApp { diff --git a/include/apps/_experiments/power_demo.h b/include/apps/_experiments/power_demo.h index 13ea3dedb..893a5d995 100644 --- a/include/apps/_experiments/power_demo.h +++ b/include/apps/_experiments/power_demo.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppPowerDemo : public OswApp { public: diff --git a/include/apps/_experiments/runtime_test.h b/include/apps/_experiments/runtime_test.h index 84929fb1a..a84792d93 100644 --- a/include/apps/_experiments/runtime_test.h +++ b/include/apps/_experiments/runtime_test.h @@ -4,7 +4,7 @@ #include -#include "osw_app.h" +#include class MiniIotClient; class OswAppRuntimeTest : public OswApp { diff --git a/include/apps/_experiments/show_display_size.h b/include/apps/_experiments/show_display_size.h index 1d6cae629..c6e6732ef 100644 --- a/include/apps/_experiments/show_display_size.h +++ b/include/apps/_experiments/show_display_size.h @@ -3,7 +3,7 @@ #include -#include "osw_app.h" +#include class OswAppShowDisplaySize : public OswApp { public: diff --git a/include/apps/clock/OswAppAlarm.h b/include/apps/clock/OswAppAlarm.h index 9d327b47d..51e2b7932 100644 --- a/include/apps/clock/OswAppAlarm.h +++ b/include/apps/clock/OswAppAlarm.h @@ -3,10 +3,9 @@ #include "../lib/date/date.h" -#include +#include #include #include -#include "apps/main/switcher.h" #include "./services/NotifierClient.h" #include @@ -22,7 +21,7 @@ class OswAppAlarm : public OswApp { DAY_PICKER }; - OswAppAlarm(OswAppSwitcher* clockAppSwitcher); + OswAppAlarm(); void setup() override; void loop() override; void stop() override; @@ -36,7 +35,6 @@ class OswAppAlarm : public OswApp { void resetAlarmState(); void listAlarms(); - OswAppSwitcher* clockAppSwitcher{}; NotifierClient notifierClient{"org.open-smartwatch.osw.alarm"}; AlarmState state{}; unsigned char step{}; diff --git a/include/apps/clock/OswAppTimer.h b/include/apps/clock/OswAppTimer.h index 5183e99a6..684315b7d 100644 --- a/include/apps/clock/OswAppTimer.h +++ b/include/apps/clock/OswAppTimer.h @@ -3,10 +3,9 @@ #include "../lib/date/date.h" -#include "osw_app.h" +#include #include #include -#include "apps/main/switcher.h" #include "./services/NotifierClient.h" #include @@ -20,7 +19,7 @@ class OswAppTimer : public OswApp { PAUSED }; - OswAppTimer(OswAppSwitcher* clockAppSwitcher); + OswAppTimer(); void setup() override; void loop() override; void stop() override; @@ -41,7 +40,6 @@ class OswAppTimer : public OswApp { void drawNumber(const int number, const int index); void drawTime(const int totalSeconds); - OswAppSwitcher* clockAppSwitcher{}; NotifierClient notifierClient{"org.open-smartwatch.osw.timer"}; TimerState state{}; unsigned char step{}; diff --git a/include/apps/clock/stopwatch.h b/include/apps/clock/stopwatch.h index dca2956df..32bc47d0e 100644 --- a/include/apps/clock/stopwatch.h +++ b/include/apps/clock/stopwatch.h @@ -2,7 +2,7 @@ #include #include -#include "osw_app.h" +#include #define maxLaps 36 #define lapsPerPage 5 diff --git a/include/apps/examples/OswAppExampleV1.h b/include/apps/examples/OswAppExampleV1.h new file mode 100644 index 000000000..94da24e69 --- /dev/null +++ b/include/apps/examples/OswAppExampleV1.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class OswAppExampleV1 : public OswAppV1 { + public: + OswAppExampleV1(); + + void setup() override; + void loop() override; + void stop() override; + + private: + // define global scope variables + bool red = false; + bool showImage = false; + unsigned int start = 0; + unsigned int counter = 0; + OswImage image; +}; diff --git a/include/apps/examples/OswAppExampleV2.h b/include/apps/examples/OswAppExampleV2.h new file mode 100644 index 000000000..adc4322be --- /dev/null +++ b/include/apps/examples/OswAppExampleV2.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +class OswAppExampleV2 : public OswAppV2 { + public: + OswAppExampleV2(); + + const char* getAppId() override; + const char* getAppName() override; + + void onStart() override; + void onLoop() override; + void onDraw() override; + void onDrawOverlay() override; + void onStop() override; + void onButton(Button id, bool up, ButtonStateNames state) override; + + private: + // define global scope variables + bool red = false; + bool showImage = false; + unsigned int start = 0; + unsigned int counter = 0; + OswImage image; +}; diff --git a/include/apps/examples/fonts/fonts_example.h b/include/apps/examples/fonts/fonts_example.h index f80cc303c..727eeef0a 100644 --- a/include/apps/examples/fonts/fonts_example.h +++ b/include/apps/examples/fonts/fonts_example.h @@ -1,7 +1,7 @@ #ifndef OSW_APP_FONTS_EXAMPLE_H #define OSW_APP_FONTS_EXAMPLE_H -#include +#include class OswAppFontsExample: public OswApp { public: OswAppFontsExample(void) {}; diff --git a/include/apps/games/brick_breaker.h b/include/apps/games/brick_breaker.h index 86c63db20..b0f3047d1 100644 --- a/include/apps/games/brick_breaker.h +++ b/include/apps/games/brick_breaker.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include class OswAppBrickBreaker : public OswApp { public: diff --git a/include/apps/games/snake_game.h b/include/apps/games/snake_game.h index 57face9d2..b421b971a 100644 --- a/include/apps/games/snake_game.h +++ b/include/apps/games/snake_game.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include class OswAppSnakeGame : public OswApp { public: diff --git a/include/apps/main/luaapp.h b/include/apps/main/luaapp.h index c7fbee15d..4d3dce17d 100644 --- a/include/apps/main/luaapp.h +++ b/include/apps/main/luaapp.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include #define LUA_SETUP_FUNC "setup" #define LUA_LOOP_FUNC "loop" diff --git a/include/apps/main/map.h b/include/apps/main/map.h index 884391498..6e2cc947d 100644 --- a/include/apps/main/map.h +++ b/include/apps/main/map.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include class OswAppMap : public OswApp { public: diff --git a/include/apps/main/switcher.h b/include/apps/main/switcher.h deleted file mode 100644 index c28c90731..000000000 --- a/include/apps/main/switcher.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include -#include -#include - -#include - -enum OswAppSwitcherType { SHORT_PRESS, LONG_PRESS }; - -class OswAppSwitcher : public OswApp { - public: - OswAppSwitcher(Button btn, OswAppSwitcherType type, bool enableAutoSleep, bool enableSleep, uint16_t* rtcAppIndex) { - _btn = btn; - _type = type; - _enableAutoSleep = enableAutoSleep; - _enableSleep = enableSleep; - _rtcAppIndex = rtcAppIndex; - } - OswAppSwitcher() {}; - virtual void setup() override; - virtual void loop() override; -#ifdef OSW_EMULATOR - virtual void loopDebug() override; -#endif - virtual void stop() override; - void paginationDisable(); - void paginationEnable(); - void registerApp(OswApp* app); - ~OswAppSwitcher() {}; - - private: - void cycleApp(); - void sleep(); - Button _btn = BUTTON_1; - OswAppSwitcherType _type = LONG_PRESS; - std::vector _apps; - uint16_t* _rtcAppIndex; - uint16_t _appCount = 0; - bool _pagination = true; - bool _paginationIndicator = false; - bool _enableAutoSleep = false; - bool _checked = false; - bool _enableSleep; - bool _doSleep = false; - bool _doSwitch = false; - long appOnScreenSince = 0; - unsigned long _timeForLongPress = APPSWITCHER_LONG_PRESS; - unsigned long _timeForSleepPress = APPSWITCHER_SLEEP_TIMEOUT; -}; diff --git a/include/apps/tools/OswAppBLEMediaCtrl.h b/include/apps/tools/OswAppBLEMediaCtrl.h index 74ed095b2..aed7dc926 100644 --- a/include/apps/tools/OswAppBLEMediaCtrl.h +++ b/include/apps/tools/OswAppBLEMediaCtrl.h @@ -2,7 +2,7 @@ #pragma once #include -#include "osw_app.h" +#include class OswAppBLEMediaCtrl : public OswApp { public: diff --git a/include/apps/tools/OswAppCalculator.h b/include/apps/tools/OswAppCalculator.h index 59ee47996..7755d28dc 100644 --- a/include/apps/tools/OswAppCalculator.h +++ b/include/apps/tools/OswAppCalculator.h @@ -2,7 +2,7 @@ #include #include -#include "osw_app.h" +#include class OswAppCalculator : public OswApp { public: diff --git a/include/apps/tools/OswAppDistStats.h b/include/apps/tools/OswAppDistStats.h index a53f1b69b..7f3ff4af5 100644 --- a/include/apps/tools/OswAppDistStats.h +++ b/include/apps/tools/OswAppDistStats.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include class OswAppDistStats : public OswApp { public: diff --git a/include/apps/tools/OswAppFitnessStats.h b/include/apps/tools/OswAppFitnessStats.h index 5d09a7ffc..f22cb3bde 100644 --- a/include/apps/tools/OswAppFitnessStats.h +++ b/include/apps/tools/OswAppFitnessStats.h @@ -2,7 +2,7 @@ #include #include -#include "osw_app.h" +#include class OswAppFitnessStats : public OswApp { public: diff --git a/include/apps/tools/OswAppFlashLight.h b/include/apps/tools/OswAppFlashLight.h index 21c3340f8..bccd4370e 100644 --- a/include/apps/tools/OswAppFlashLight.h +++ b/include/apps/tools/OswAppFlashLight.h @@ -2,20 +2,23 @@ #include #include -#include "osw_app.h" +#include - -class OswAppFlashLight : public OswApp { +class OswAppFlashLight : public OswAppV2 { public: - OswAppFlashLight(void) { - ui = OswUI::getInstance(); - }; - ~OswAppFlashLight() {}; - private: - OswUI* ui; - virtual void setup() override; - virtual void loop() override; //checks for button presses and turns the flashlight on/off - virtual void stop() override; - void draw(short flashlightBrightness, bool on); + OswAppFlashLight(); + virtual ~OswAppFlashLight(); + + const char* getAppId() override; + const char* getAppName() override; + const OswIcon& getAppIcon() override; + void onStart() override; + void onDraw() override; + void onStop() override; + + virtual void onButton(Button id, bool up, OswAppV2::ButtonStateNames state) override; + private: + bool on = false; + short flashlightBrightness = 255; // seperat variable allows to change the Brightness }; \ No newline at end of file diff --git a/include/apps/tools/OswAppKcalStats.h b/include/apps/tools/OswAppKcalStats.h index 581476945..c034260c0 100644 --- a/include/apps/tools/OswAppKcalStats.h +++ b/include/apps/tools/OswAppKcalStats.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include class OswAppKcalStats : public OswApp { public: diff --git a/include/apps/tools/OswAppPrintDebug.h b/include/apps/tools/OswAppPrintDebug.h index 813289d3a..26c992322 100644 --- a/include/apps/tools/OswAppPrintDebug.h +++ b/include/apps/tools/OswAppPrintDebug.h @@ -2,7 +2,7 @@ #pragma once #include -#include "osw_app.h" +#include class OswAppPrintDebug : public OswApp { public: diff --git a/include/apps/tools/OswAppStepStats.h b/include/apps/tools/OswAppStepStats.h index 0d2f057e0..336700acf 100644 --- a/include/apps/tools/OswAppStepStats.h +++ b/include/apps/tools/OswAppStepStats.h @@ -4,7 +4,7 @@ #include #include -#include "osw_app.h" +#include class OswAppStepStats : public OswApp { public: @@ -13,6 +13,9 @@ class OswAppStepStats : public OswApp { }; virtual void setup() override; virtual void loop() override; +#ifdef OSW_EMULATOR + virtual void loopDebug() override; +#endif virtual void stop() override; ~OswAppStepStats() {}; static void drawInfoPanel(OswUI* ui, uint32_t pos, uint32_t lastWeekData, uint32_t todayData, uint32_t average, uint32_t total, const String& unit = String("")); diff --git a/include/apps/tools/OswAppTimeConfig.h b/include/apps/tools/OswAppTimeConfig.h index 091772621..c69bc4d70 100644 --- a/include/apps/tools/OswAppTimeConfig.h +++ b/include/apps/tools/OswAppTimeConfig.h @@ -2,14 +2,12 @@ #include #include -#include "apps/main/switcher.h" -#include "osw_app.h" +#include class OswAppTimeConfig : public OswApp { public: - OswAppTimeConfig(OswAppSwitcher* settingsAppSwitcher) { + OswAppTimeConfig() { ui = OswUI::getInstance(); - this->settingsAppSwitcher = settingsAppSwitcher; }; virtual void setup() override; virtual void loop() override; @@ -25,5 +23,4 @@ class OswAppTimeConfig : public OswApp { int8_t manualSettingStep = 0; int16_t manualSettingTimestamp[11]; OswUI* ui = nullptr; - OswAppSwitcher* settingsAppSwitcher = nullptr; }; \ No newline at end of file diff --git a/include/apps/tools/OswAppTutorial.h b/include/apps/tools/OswAppTutorial.h new file mode 100644 index 000000000..7f843fa92 --- /dev/null +++ b/include/apps/tools/OswAppTutorial.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include + +class OswAppTutorial : public OswAppV2 { + public: + OswAppTutorial(); + virtual ~OswAppTutorial(); + + const char* getAppId() override; + const char* getAppName() override; + const OswIcon& getAppIcon() override; + + void onStart() override; + void onLoop() override; + void onDraw() override; + void onStop() override; + + virtual void onButton(Button id, bool up, OswAppV2::ButtonStateNames state) override; +#ifdef OSW_EMULATOR + void onLoopDebug() override; +#endif + + bool changeRootAppIfNecessary(); + private: + OswAppV2* previousRootApp = nullptr; + static OswIconProgmem oswIcon; + unsigned screen = 0; + unsigned currentScreen = 0; + unsigned char hsv = 0; + std::optional