From 0b14db4fb33d4466a02c0c85b01321bece5d6537 Mon Sep 17 00:00:00 2001 From: SG Date: Tue, 15 Jun 2021 21:01:56 +1000 Subject: [PATCH] C++ apps: templated scene controller (#517) * C++ apps: templated scene controller * templated app: fix type names * templated app: text store component * Applications: add "Templated Scene" application * templated app: refractoring * Gui module byte input: fix docs * templated app: new byte input scene * templated app: dialog ex view module * templated app: popup view module * templated app: dialog-ex view module, fix docs * templated app: text input view module * Gui module text input: fix docs * Furi: duplicated include * templated app: record holder (controller) class * templated app: view modules can now be accessed via cast * templated app: remove unused includes * templated app: fix return code --- applications/applications.c | 5 + applications/gui/modules/byte_input.h | 2 +- applications/gui/modules/text_input.h | 22 ++- .../scene/scened-app-scene-byte-input.cpp | 35 ++++ .../scene/scened-app-scene-byte-input.h | 19 ++ .../scene/scened-app-scene-start.cpp | 47 +++++ .../scene/scened-app-scene-start.h | 13 ++ .../scened-app-launcher.cpp | 10 + .../scened-app-example/scened-app.cpp | 20 ++ applications/scened-app-example/scened-app.h | 47 +++++ core/furi.h | 1 - lib/app-scened-template/generic-scene.hpp | 9 + lib/app-scened-template/record-controller.hpp | 46 +++++ lib/app-scened-template/scene-controller.hpp | 178 ++++++++++++++++++ lib/app-scened-template/text-store.cpp | 18 ++ lib/app-scened-template/text-store.h | 12 ++ lib/app-scened-template/typeindex_no_rtti.hpp | 121 ++++++++++++ lib/app-scened-template/view-controller.hpp | 163 ++++++++++++++++ .../view-modules/byte-input-vm.cpp | 32 ++++ .../view-modules/byte-input-vm.h | 37 ++++ .../view-modules/dialog-ex-vm.cpp | 61 ++++++ .../view-modules/dialog-ex-vm.h | 73 +++++++ .../view-modules/generic-view-module.h | 10 + .../view-modules/popup-vm.cpp | 54 ++++++ .../view-modules/popup-vm.h | 68 +++++++ .../view-modules/submenu-vm.cpp | 33 ++++ .../view-modules/submenu-vm.h | 43 +++++ .../view-modules/text-input-vm.cpp | 30 +++ .../view-modules/text-input-vm.h | 35 ++++ lib/lib.mk | 7 +- 30 files changed, 1242 insertions(+), 9 deletions(-) create mode 100644 applications/scened-app-example/scene/scened-app-scene-byte-input.cpp create mode 100644 applications/scened-app-example/scene/scened-app-scene-byte-input.h create mode 100644 applications/scened-app-example/scene/scened-app-scene-start.cpp create mode 100644 applications/scened-app-example/scene/scened-app-scene-start.h create mode 100644 applications/scened-app-example/scened-app-launcher.cpp create mode 100644 applications/scened-app-example/scened-app.cpp create mode 100644 applications/scened-app-example/scened-app.h create mode 100644 lib/app-scened-template/generic-scene.hpp create mode 100644 lib/app-scened-template/record-controller.hpp create mode 100644 lib/app-scened-template/scene-controller.hpp create mode 100644 lib/app-scened-template/text-store.cpp create mode 100644 lib/app-scened-template/text-store.h create mode 100644 lib/app-scened-template/typeindex_no_rtti.hpp create mode 100644 lib/app-scened-template/view-controller.hpp create mode 100644 lib/app-scened-template/view-modules/byte-input-vm.cpp create mode 100644 lib/app-scened-template/view-modules/byte-input-vm.h create mode 100644 lib/app-scened-template/view-modules/dialog-ex-vm.cpp create mode 100644 lib/app-scened-template/view-modules/dialog-ex-vm.h create mode 100644 lib/app-scened-template/view-modules/generic-view-module.h create mode 100644 lib/app-scened-template/view-modules/popup-vm.cpp create mode 100644 lib/app-scened-template/view-modules/popup-vm.h create mode 100644 lib/app-scened-template/view-modules/submenu-vm.cpp create mode 100644 lib/app-scened-template/view-modules/submenu-vm.h create mode 100644 lib/app-scened-template/view-modules/text-input-vm.cpp create mode 100644 lib/app-scened-template/view-modules/text-input-vm.h diff --git a/applications/applications.c b/applications/applications.c index 1f3435f8..9db0c828 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -39,6 +39,7 @@ int32_t app_accessor(void* p); int32_t internal_storage_task(void* p); int32_t app_archive(void* p); int32_t notification_app(void* p); +int32_t scened_app(void* p); // On system start hooks declaration void irda_cli_init(); @@ -293,6 +294,10 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = { .stack_size = 1024, .icon = A_Plugins_14}, #endif + +#ifdef APP_SCENED + {.app = scened_app, .name = "Templated Scene", .stack_size = 1024, .icon = A_Plugins_14}, +#endif }; const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication); diff --git a/applications/gui/modules/byte_input.h b/applications/gui/modules/byte_input.h index 3f3856dc..8f80b5b3 100644 --- a/applications/gui/modules/byte_input.h +++ b/applications/gui/modules/byte_input.h @@ -46,7 +46,7 @@ void byte_input_free(ByteInput* byte_input); View* byte_input_get_view(ByteInput* byte_input); /** - * @brief Deinitialize and free byte input + * @brief Set byte input result callback * * @param byte_input byte input instance * @param input_callback input callback fn diff --git a/applications/gui/modules/text_input.h b/applications/gui/modules/text_input.h index f6fb3fcd..8e5d8405 100644 --- a/applications/gui/modules/text_input.h +++ b/applications/gui/modules/text_input.h @@ -9,23 +9,31 @@ extern "C" { typedef struct TextInput TextInput; typedef void (*TextInputCallback)(void* context, char* text); -/* Allocate and initialize text input - * This text input is used to enter string +/** + * @brief Allocate and initialize text input + * This text input is used to enter string + * */ TextInput* text_input_alloc(); -/* Deinitialize and free text input +/** + * @brief Deinitialize and free text input + * * @param text_input - Text input instance */ void text_input_free(TextInput* text_input); -/* Get text input view +/** + * @brief Get text input view + * * @param text_input - Text input instance * @return View instance that can be used for embedding */ View* text_input_get_view(TextInput* text_input); -/* Deinitialize and free text input +/** + * @brief Set text input result callback + * * @param text_input - Text input instance * @param callback - callback fn * @param callback_context - callback context @@ -39,7 +47,9 @@ void text_input_set_result_callback( char* text, uint8_t max_text_length); -/* Set text input header text +/** + * @brief Set text input header text + * * @param text input - Text input instance * @param text - text to be shown */ diff --git a/applications/scened-app-example/scene/scened-app-scene-byte-input.cpp b/applications/scened-app-example/scene/scened-app-scene-byte-input.cpp new file mode 100644 index 00000000..19d81bc5 --- /dev/null +++ b/applications/scened-app-example/scene/scened-app-scene-byte-input.cpp @@ -0,0 +1,35 @@ +#include "scened-app-scene-byte-input.h" + +void ScenedAppSceneByteInput::on_enter(ScenedApp* app, bool need_restore) { + ByteInputVM* byte_input = app->view_controller; + auto callback = cbc::obtain_connector(this, &ScenedAppSceneByteInput::result_callback); + + byte_input->set_result_callback(callback, NULL, app, data, 4); + byte_input->set_header_text("Enter the key"); + + app->view_controller.switch_to(); +} + +bool ScenedAppSceneByteInput::on_event(ScenedApp* app, ScenedApp::Event* event) { + bool consumed = false; + + if(event->type == ScenedApp::EventType::ByteEditResult) { + app->scene_controller.switch_to_previous_scene(); + consumed = true; + } + + return consumed; +} + +void ScenedAppSceneByteInput::on_exit(ScenedApp* app) { + app->view_controller.get()->clean(); +} + +void ScenedAppSceneByteInput::result_callback(void* context, uint8_t* bytes, uint8_t bytes_count) { + ScenedApp* app = static_cast(context); + ScenedApp::Event event; + + event.type = ScenedApp::EventType::ByteEditResult; + + app->view_controller.send_event(&event); +} diff --git a/applications/scened-app-example/scene/scened-app-scene-byte-input.h b/applications/scened-app-example/scene/scened-app-scene-byte-input.h new file mode 100644 index 00000000..de73ab12 --- /dev/null +++ b/applications/scened-app-example/scene/scened-app-scene-byte-input.h @@ -0,0 +1,19 @@ +#pragma once +#include "../scened-app.h" + +class ScenedAppSceneByteInput : public GenericScene { +public: + void on_enter(ScenedApp* app, bool need_restore) final; + bool on_event(ScenedApp* app, ScenedApp::Event* event) final; + void on_exit(ScenedApp* app) final; + +private: + void result_callback(void* context, uint8_t* bytes, uint8_t bytes_count); + + uint8_t data[4] = { + 0x01, + 0xA2, + 0xF4, + 0xD3, + }; +}; \ No newline at end of file diff --git a/applications/scened-app-example/scene/scened-app-scene-start.cpp b/applications/scened-app-example/scene/scened-app-scene-start.cpp new file mode 100644 index 00000000..0fe0f8a9 --- /dev/null +++ b/applications/scened-app-example/scene/scened-app-scene-start.cpp @@ -0,0 +1,47 @@ +#include "scened-app-scene-start.h" + +typedef enum { + SubmenuByteInput, +} SubmenuIndex; + +void ScenedAppSceneStart::on_enter(ScenedApp* app, bool need_restore) { + auto submenu = app->view_controller.get(); + auto callback = cbc::obtain_connector(this, &ScenedAppSceneStart::submenu_callback); + + submenu->add_item("Byte Input", SubmenuByteInput, callback, app); + + if(need_restore) { + submenu->set_selected_item(submenu_item_selected); + } + app->view_controller.switch_to(); +} + +bool ScenedAppSceneStart::on_event(ScenedApp* app, ScenedApp::Event* event) { + bool consumed = false; + + if(event->type == ScenedApp::EventType::MenuSelected) { + submenu_item_selected = event->payload.menu_index; + switch(event->payload.menu_index) { + case SubmenuByteInput: + app->scene_controller.switch_to_next_scene(ScenedApp::SceneType::ByteInputScene); + break; + } + consumed = true; + } + + return consumed; +} + +void ScenedAppSceneStart::on_exit(ScenedApp* app) { + app->view_controller.get()->clean(); +} + +void ScenedAppSceneStart::submenu_callback(void* context, uint32_t index) { + ScenedApp* app = static_cast(context); + ScenedApp::Event event; + + event.type = ScenedApp::EventType::MenuSelected; + event.payload.menu_index = index; + + app->view_controller.send_event(&event); +} diff --git a/applications/scened-app-example/scene/scened-app-scene-start.h b/applications/scened-app-example/scene/scened-app-scene-start.h new file mode 100644 index 00000000..d585ea1e --- /dev/null +++ b/applications/scened-app-example/scene/scened-app-scene-start.h @@ -0,0 +1,13 @@ +#pragma once +#include "../scened-app.h" + +class ScenedAppSceneStart : public GenericScene { +public: + void on_enter(ScenedApp* app, bool need_restore) final; + bool on_event(ScenedApp* app, ScenedApp::Event* event) final; + void on_exit(ScenedApp* app) final; + +private: + void submenu_callback(void* context, uint32_t index); + uint32_t submenu_item_selected = 0; +}; \ No newline at end of file diff --git a/applications/scened-app-example/scened-app-launcher.cpp b/applications/scened-app-example/scened-app-launcher.cpp new file mode 100644 index 00000000..58f52f11 --- /dev/null +++ b/applications/scened-app-example/scened-app-launcher.cpp @@ -0,0 +1,10 @@ +#include "scened-app.h" + +// app enter function +extern "C" int32_t scened_app(void* p) { + ScenedApp* app = new ScenedApp(); + app->run(); + delete app; + + return 0; +} diff --git a/applications/scened-app-example/scened-app.cpp b/applications/scened-app-example/scened-app.cpp new file mode 100644 index 00000000..56994218 --- /dev/null +++ b/applications/scened-app-example/scened-app.cpp @@ -0,0 +1,20 @@ +#include "scened-app.h" +#include "scene/scened-app-scene-start.h" +#include "scene/scened-app-scene-byte-input.h" + +ScenedApp::ScenedApp() + : scene_controller{this} + , text_store{128} + , notification{"notification"} { +} + +ScenedApp::~ScenedApp() { +} + +void ScenedApp::run() { + scene_controller.add_scene(SceneType::Start, new ScenedAppSceneStart()); + scene_controller.add_scene(SceneType::ByteInputScene, new ScenedAppSceneByteInput()); + + notification_message(notification, &sequence_blink_green_10); + scene_controller.process(100); +} \ No newline at end of file diff --git a/applications/scened-app-example/scened-app.h b/applications/scened-app-example/scened-app.h new file mode 100644 index 00000000..d45dbf83 --- /dev/null +++ b/applications/scened-app-example/scened-app.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +class ScenedApp { +public: + enum class EventType : uint8_t { + GENERIC_EVENT_ENUM_VALUES, + MenuSelected, + ByteEditResult, + }; + + enum class SceneType : uint8_t { + GENERIC_SCENE_ENUM_VALUES, + ByteInputScene, + }; + + class Event { + public: + union { + int32_t menu_index; + } payload; + + EventType type; + }; + + SceneController, ScenedApp> scene_controller; + TextStore text_store; + ViewController view_controller; + RecordController notification; + + ~ScenedApp(); + ScenedApp(); + + void run(); +}; \ No newline at end of file diff --git a/core/furi.h b/core/furi.h index 9c777f36..cdb1ab8b 100644 --- a/core/furi.h +++ b/core/furi.h @@ -11,7 +11,6 @@ #include #include #include -#include #include #include diff --git a/lib/app-scened-template/generic-scene.hpp b/lib/app-scened-template/generic-scene.hpp new file mode 100644 index 00000000..c9e823b2 --- /dev/null +++ b/lib/app-scened-template/generic-scene.hpp @@ -0,0 +1,9 @@ +template class GenericScene { +public: + virtual void on_enter(TApp* app, bool need_restore) = 0; + virtual bool on_event(TApp* app, typename TApp::Event* event) = 0; + virtual void on_exit(TApp* app) = 0; + virtual ~GenericScene(){}; + +private: +}; \ No newline at end of file diff --git a/lib/app-scened-template/record-controller.hpp b/lib/app-scened-template/record-controller.hpp new file mode 100644 index 00000000..af8db58c --- /dev/null +++ b/lib/app-scened-template/record-controller.hpp @@ -0,0 +1,46 @@ +#pragma once +#include + +/** + * @brief Class for opening, casting, holding and closing records + * + * @tparam TRecordClass record class + */ +template class RecordController { +public: + /** + * @brief Construct a new Record Controller object for record with record name + * + * @param record_name record name + */ + RecordController(const char* record_name) { + name = record_name; + value = static_cast(furi_record_open(name)); + }; + + ~RecordController() { + furi_record_close(name); + } + + /** + * @brief Record getter + * + * @return TRecordClass* record value + */ + TRecordClass* get() { + return value; + } + + /** + * @brief Record getter (by cast) + * + * @return TRecordClass* record value + */ + operator TRecordClass*() const { + return value; + } + +private: + const char* name; + TRecordClass* value; +}; \ No newline at end of file diff --git a/lib/app-scened-template/scene-controller.hpp b/lib/app-scened-template/scene-controller.hpp new file mode 100644 index 00000000..362edbe9 --- /dev/null +++ b/lib/app-scened-template/scene-controller.hpp @@ -0,0 +1,178 @@ +#include +#include +#include + +#define GENERIC_SCENE_ENUM_VALUES Uninitalized, Exit, Start +#define GENERIC_EVENT_ENUM_VALUES Tick, Back + +/** + * @brief Controller for scene navigation in application + * + * @tparam TScene generic scene class + * @tparam TApp application class + */ +template class SceneController { +public: + /** + * @brief Add scene to scene container + * + * @param scene_index scene index + * @param scene_pointer scene object pointer + */ + void add_scene(typename TApp::SceneType scene_index, TScene* scene_pointer) { + furi_check(scenes.count(scene_index) == 0); + scenes[scene_index] = scene_pointer; + } + + /** + * @brief Switch to next scene and store current scene in previous scenes list + * + * @param scene_index next scene index + * @param need_restore true, if we want the scene to restore its parameters + */ + void switch_to_next_scene(typename TApp::SceneType scene_index, bool need_restore = false) { + previous_scenes_list.push_front(current_scene_index); + switch_to_scene(scene_index, need_restore); + } + + /** + * @brief Switch to next scene without ability to return to current scene + * + * @param scene_index next scene index + * @param need_restore true, if we want the scene to restore its parameters + */ + void switch_to_scene(typename TApp::SceneType scene_index, bool need_restore = false) { + if(scene_index != TApp::SceneType::Exit) { + scenes[current_scene_index]->on_exit(app); + current_scene_index = scene_index; + scenes[current_scene_index]->on_enter(app, need_restore); + } + } + + /** + * @brief Search the scene in the list of previous scenes and switch to it + * + * @param scene_index_list list of scene indexes to which you want to switch + */ + void search_and_switch_to_previous_scene( + const std::initializer_list& scene_index_list) { + auto previous_scene_index = TApp::SceneType::Start; + bool scene_found = false; + + while(!scene_found) { + previous_scene_index = get_previous_scene_index(); + for(const auto& element : scene_index_list) { + if(previous_scene_index == element) { + scene_found = true; + break; + } + } + } + + switch_to_scene(previous_scene_index, true); + } + + /** + * @brief Start application main cycle + * + * @param tick_length_ms tick event length in milliseconds + */ + void process(uint32_t tick_length_ms = 100) { + typename TApp::Event event; + bool consumed; + bool exit = false; + + current_scene_index = TApp::SceneType::Start; + scenes[current_scene_index]->on_enter(app, false); + + while(!exit) { + app->view_controller.receive_event(&event); + + consumed = scenes[current_scene_index]->on_event(app, &event); + + if(!consumed) { + if(event.type == TApp::EventType::Back) { + exit = switch_to_previous_scene(); + } + } + }; + + scenes[current_scene_index]->on_exit(app); + } + + /** + * @brief Switch to previous scene + * + * @param count how many steps back + * @return true if app need to exit + */ + bool switch_to_previous_scene(uint8_t count = 1) { + auto previous_scene_index = TApp::SceneType::Start; + + for(uint8_t i = 0; i < count; i++) previous_scene_index = get_previous_scene_index(); + + if(previous_scene_index == TApp::SceneType::Exit) return true; + + switch_to_scene(previous_scene_index, true); + return false; + } + + /** + * @brief Construct a new Scene Controller object + * + * @param app_pointer pointer to application class + */ + SceneController(TApp* app_pointer) { + app = app_pointer; + current_scene_index = TApp::SceneType::Uninitalized; + } + + /** + * @brief Destroy the Scene Controller object + * + */ + ~SceneController() { + for(auto& it : scenes) delete it.second; + } + +private: + /** + * @brief Scenes pointers container + * + */ + std::map scenes; + + /** + * @brief List of indexes of previous scenes + * + */ + std::forward_list previous_scenes_list; + + /** + * @brief Current scene index holder + * + */ + typename TApp::SceneType current_scene_index; + + /** + * @brief Application pointer holder + * + */ + TApp* app; + + /** + * @brief Get the previous scene index + * + * @return previous scene index + */ + typename TApp::SceneType get_previous_scene_index() { + auto scene_index = TApp::SceneType::Exit; + + if(!previous_scenes_list.empty()) { + scene_index = previous_scenes_list.front(); + previous_scenes_list.pop_front(); + } + + return scene_index; + } +}; \ No newline at end of file diff --git a/lib/app-scened-template/text-store.cpp b/lib/app-scened-template/text-store.cpp new file mode 100644 index 00000000..b46adbb9 --- /dev/null +++ b/lib/app-scened-template/text-store.cpp @@ -0,0 +1,18 @@ +#include "text-store.h" +#include + +TextStore::TextStore(uint8_t _text_size) + : text_size(_text_size) { + text = static_cast(malloc(text_size + 1)); +} + +TextStore::~TextStore() { + free(text); +} + +void TextStore::set_text_store(const char* _text...) { + va_list args; + va_start(args, _text); + vsnprintf(text, text_size, _text, args); + va_end(args); +} diff --git a/lib/app-scened-template/text-store.h b/lib/app-scened-template/text-store.h new file mode 100644 index 00000000..9584df47 --- /dev/null +++ b/lib/app-scened-template/text-store.h @@ -0,0 +1,12 @@ +#pragma once +#include + +class TextStore { +public: + TextStore(uint8_t text_size); + ~TextStore(); + + void set_text_store(const char* text...); + const uint8_t text_size; + char* text; +}; \ No newline at end of file diff --git a/lib/app-scened-template/typeindex_no_rtti.hpp b/lib/app-scened-template/typeindex_no_rtti.hpp new file mode 100644 index 00000000..be350cc9 --- /dev/null +++ b/lib/app-scened-template/typeindex_no_rtti.hpp @@ -0,0 +1,121 @@ +/* + * type_index without RTTI + * + * Copyright frickiericker 2016. + * Distributed under the Boost Software License, Version 1.0. + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +namespace ext { +/** + * Dummy type for tag-dispatching. + */ +template struct tag_type {}; + +/** + * A value of tag_type. + */ +template constexpr tag_type tag{}; + +/** + * A type_index implementation without RTTI. + */ +struct type_index { + /** + * Creates a type_index object for the specified type. + */ + template type_index(tag_type) noexcept : hash_code_{index} { + } + + /** + * Returns the hash code. + */ + std::size_t hash_code() const noexcept { + return hash_code_; + } + +private: + /** + * Unique integral index associated to template type argument. + */ + template static std::size_t const index; + + /** + * Global counter for generating index values. + */ + static std::size_t& counter() noexcept { + static std::size_t counter_; + return counter_; + } + +private: + std::size_t hash_code_; +}; + +template std::size_t const type_index::index = type_index::counter()++; + +/** + * Creates a type_index object for the specified type. + * + * Equivalent to `ext::type_index{ext::tag}`. + */ +template type_index make_type_index() noexcept { + return tag; +} + +inline bool operator==(type_index const& a, type_index const& b) noexcept { + return a.hash_code() == b.hash_code(); +} + +inline bool operator!=(type_index const& a, type_index const& b) noexcept { + return !(a == b); +} + +inline bool operator<(type_index const& a, type_index const& b) noexcept { + return a.hash_code() < b.hash_code(); +} + +inline bool operator<=(type_index const& a, type_index const& b) noexcept { + return a.hash_code() <= b.hash_code(); +} + +inline bool operator>(type_index const& a, type_index const& b) noexcept { + return !(a <= b); +} + +inline bool operator>=(type_index const& a, type_index const& b) noexcept { + return !(a < b); +} +} + +template <> struct std::hash { + using argument_type = ext::type_index; + using result_type = std::size_t; + + result_type operator()(argument_type const& t) const noexcept { + return t.hash_code(); + } +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-controller.hpp b/lib/app-scened-template/view-controller.hpp new file mode 100644 index 00000000..7292107b --- /dev/null +++ b/lib/app-scened-template/view-controller.hpp @@ -0,0 +1,163 @@ +#pragma once +#include "view-modules/generic-view-module.h" +#include +#include +#include +#include +#include "typeindex_no_rtti.hpp" + +/** + * @brief Controller for switching application views and handling inputs and events + * + * @tparam TApp application class + * @tparam TViewModules variadic list of ViewModules + */ +template class ViewController { +public: + ViewController() { + event_queue = osMessageQueueNew(10, sizeof(typename TApp::Event), NULL); + + view_dispatcher = view_dispatcher_alloc(); + previous_view_callback_pointer = cbc::obtain_connector( + this, &ViewController::previous_view_callback); + + [](...) { + }((this->add_view(ext::make_type_index().hash_code(), new TViewModules()), + 0)...); + + gui = static_cast(furi_record_open("gui")); + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + }; + + ~ViewController() { + for(auto& it : holder) { + view_dispatcher_remove_view(view_dispatcher, static_cast(it.first)); + delete it.second; + } + + view_dispatcher_free(view_dispatcher); + osMessageQueueDelete(event_queue); + } + + /** + * @brief Get ViewModule pointer + * + * @tparam T Concrete ViewModule class + * @return T* ViewModule pointer + */ + template T* get() { + uint32_t view_index = ext::make_type_index().hash_code(); + furi_check(holder.count(view_index) != 0); + return static_cast(holder[view_index]); + } + + /** + * @brief Get ViewModule pointer by cast + * + * @tparam T Concrete ViewModule class + * @return T* ViewModule pointer + */ + template operator T*() { + uint32_t view_index = ext::make_type_index().hash_code(); + furi_check(holder.count(view_index) != 0); + return static_cast(holder[view_index]); + } + + /** + * @brief Switch view to ViewModule + * + * @tparam T Concrete ViewModule class + * @return T* ViewModule pointer + */ + template void switch_to() { + uint32_t view_index = ext::make_type_index().hash_code(); + furi_check(holder.count(view_index) != 0); + view_dispatcher_switch_to_view(view_dispatcher, view_index); + } + + /** + * @brief Receive event from app event queue + * + * @param event event pointer + */ + void receive_event(typename TApp::Event* event) { + if(osMessageQueueGet(event_queue, event, NULL, 100) != osOK) { + event->type = TApp::EventType::Tick; + } + } + + /** + * @brief Send event to app event queue + * + * @param event event pointer + */ + void send_event(typename TApp::Event* event) { + osStatus_t result = osMessageQueuePut(event_queue, event, 0, osWaitForever); + furi_check(result == osOK); + } + +private: + /** + * @brief ViewModulesHolder + * + */ + std::map holder; + + /** + * @brief App event queue + * + */ + osMessageQueueId_t event_queue; + + /** + * @brief Main ViewDispatcher pointer + * + */ + ViewDispatcher* view_dispatcher; + + /** + * @brief Gui record pointer + * + */ + Gui* gui; + + /** + * @brief Previous view callback fn pointer + * + */ + ViewNavigationCallback previous_view_callback_pointer; + + /** + * @brief Previous view callback fn + * + * @param context not used + * @return uint32_t VIEW_IGNORE + */ + uint32_t previous_view_callback(void* context) { + (void)context; + + typename TApp::Event event; + event.type = TApp::EventType::Back; + + if(event_queue != NULL) { + send_event(&event); + } + + return VIEW_IGNORE; + } + + /** + * @brief Add ViewModule to holder + * + * @param view_index view index in holder + * @param view_module view module pointer + */ + void add_view(size_t view_index, GenericViewModule* view_module) { + furi_check(holder.count(view_index) == 0); + holder[view_index] = view_module; + + View* view = view_module->get_view(); + view_dispatcher_add_view(view_dispatcher, static_cast(view_index), view); + view_set_previous_callback(view, previous_view_callback_pointer); + } +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-modules/byte-input-vm.cpp b/lib/app-scened-template/view-modules/byte-input-vm.cpp new file mode 100644 index 00000000..ff0e3c00 --- /dev/null +++ b/lib/app-scened-template/view-modules/byte-input-vm.cpp @@ -0,0 +1,32 @@ +#include "byte-input-vm.h" + +ByteInputVM::ByteInputVM() { + byte_input = byte_input_alloc(); +} + +ByteInputVM::~ByteInputVM() { + byte_input_free(byte_input); +} + +View* ByteInputVM::get_view() { + return byte_input_get_view(byte_input); +} + +void ByteInputVM::clean() { + byte_input_set_header_text(byte_input, ""); + byte_input_set_result_callback(byte_input, NULL, NULL, NULL, NULL, 0); +} + +void ByteInputVM::set_result_callback( + ByteInputCallback input_callback, + ByteChangedCallback changed_callback, + void* callback_context, + uint8_t* bytes, + uint8_t bytes_count) { + byte_input_set_result_callback( + byte_input, input_callback, changed_callback, callback_context, bytes, bytes_count); +} + +void ByteInputVM::set_header_text(const char* text) { + byte_input_set_header_text(byte_input, text); +} diff --git a/lib/app-scened-template/view-modules/byte-input-vm.h b/lib/app-scened-template/view-modules/byte-input-vm.h new file mode 100644 index 00000000..6bda39ff --- /dev/null +++ b/lib/app-scened-template/view-modules/byte-input-vm.h @@ -0,0 +1,37 @@ +#pragma once +#include "generic-view-module.h" +#include + +class ByteInputVM : public GenericViewModule { +public: + ByteInputVM(); + ~ByteInputVM() final; + View* get_view() final; + void clean() final; + + /** + * @brief Set byte input result callback + * + * @param input_callback input callback fn + * @param changed_callback changed callback fn + * @param callback_context callback context + * @param bytes buffer to use + * @param bytes_count buffer length + */ + void set_result_callback( + ByteInputCallback input_callback, + ByteChangedCallback changed_callback, + void* callback_context, + uint8_t* bytes, + uint8_t bytes_count); + + /** + * @brief Set byte input header text + * + * @param text text to be shown + */ + void set_header_text(const char* text); + +private: + ByteInput* byte_input; +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-modules/dialog-ex-vm.cpp b/lib/app-scened-template/view-modules/dialog-ex-vm.cpp new file mode 100644 index 00000000..212b0838 --- /dev/null +++ b/lib/app-scened-template/view-modules/dialog-ex-vm.cpp @@ -0,0 +1,61 @@ +#include "dialog-ex-vm.h" + +DialogExVM::DialogExVM() { + dialog_ex = dialog_ex_alloc(); +} + +DialogExVM::~DialogExVM() { + dialog_ex_free(dialog_ex); +} + +View* DialogExVM::get_view() { + return dialog_ex_get_view(dialog_ex); +} + +void DialogExVM::clean() { + set_result_callback(NULL); + set_context(NULL); + set_header(NULL, 0, 0, AlignLeft, AlignBottom); + set_text(NULL, 0, 0, AlignLeft, AlignBottom); + set_icon(-1, -1, I_ButtonCenter_7x7); + set_left_button_text(NULL); + set_center_button_text(NULL); + set_right_button_text(NULL); +} + +void DialogExVM::set_result_callback(DialogExResultCallback callback) { + dialog_ex_set_result_callback(dialog_ex, callback); +} + +void DialogExVM::set_context(void* context) { + dialog_ex_set_context(dialog_ex, context); +} + +void DialogExVM::set_header( + const char* text, + uint8_t x, + uint8_t y, + Align horizontal, + Align vertical) { + dialog_ex_set_header(dialog_ex, text, x, y, horizontal, vertical); +} + +void DialogExVM::set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical) { + dialog_ex_set_text(dialog_ex, text, x, y, horizontal, vertical); +} + +void DialogExVM::set_icon(int8_t x, int8_t y, IconName name) { + dialog_ex_set_icon(dialog_ex, x, y, name); +} + +void DialogExVM::set_left_button_text(const char* text) { + dialog_ex_set_left_button_text(dialog_ex, text); +} + +void DialogExVM::set_center_button_text(const char* text) { + dialog_ex_set_center_button_text(dialog_ex, text); +} + +void DialogExVM::set_right_button_text(const char* text) { + dialog_ex_set_right_button_text(dialog_ex, text); +} diff --git a/lib/app-scened-template/view-modules/dialog-ex-vm.h b/lib/app-scened-template/view-modules/dialog-ex-vm.h new file mode 100644 index 00000000..0f8f42ff --- /dev/null +++ b/lib/app-scened-template/view-modules/dialog-ex-vm.h @@ -0,0 +1,73 @@ +#pragma once +#include "generic-view-module.h" +#include + +class DialogExVM : public GenericViewModule { +public: + DialogExVM(); + ~DialogExVM() final; + View* get_view() final; + void clean() final; + + /** + * Set dialog result callback + * @param callback - result callback function + */ + void set_result_callback(DialogExResultCallback callback); + + /** + * Set dialog context + * @param context - context pointer, will be passed to result callback + */ + void set_context(void* context); + + /** + * Set dialog header text + * If text is null, dialog header will not be rendered + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ + void set_header(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical); + + /** + * Set dialog text + * If text is null, dialog text will not be rendered + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ + void set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical); + + /** + * Set dialog icon + * If x or y is negative, dialog icon will not be rendered + * @param x, y - icon position + * @param name - icon to be shown + */ + void set_icon(int8_t x, int8_t y, IconName name); + + /** + * Set left button text + * If text is null, left button will not be rendered and processed + * @param text - text to be shown + */ + void set_left_button_text(const char* text); + + /** + * Set center button text + * If text is null, center button will not be rendered and processed + * @param text - text to be shown + */ + void set_center_button_text(const char* text); + + /** + * Set right button text + * If text is null, right button will not be rendered and processed + * @param text - text to be shown + */ + void set_right_button_text(const char* text); + +private: + DialogEx* dialog_ex; +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-modules/generic-view-module.h b/lib/app-scened-template/view-modules/generic-view-module.h new file mode 100644 index 00000000..10f04dc3 --- /dev/null +++ b/lib/app-scened-template/view-modules/generic-view-module.h @@ -0,0 +1,10 @@ +#pragma once +#include + +class GenericViewModule { +public: + GenericViewModule(){}; + virtual ~GenericViewModule(){}; + virtual View* get_view() = 0; + virtual void clean() = 0; +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-modules/popup-vm.cpp b/lib/app-scened-template/view-modules/popup-vm.cpp new file mode 100644 index 00000000..6f490ecf --- /dev/null +++ b/lib/app-scened-template/view-modules/popup-vm.cpp @@ -0,0 +1,54 @@ +#include "popup-vm.h" +PopupVM::PopupVM() { + popup = popup_alloc(); +} + +PopupVM::~PopupVM() { + popup_free(popup); +} + +View* PopupVM::get_view() { + return popup_get_view(popup); +} + +void PopupVM::clean() { + set_callback(NULL); + set_context(NULL); + set_header(NULL, 0, 0, AlignLeft, AlignBottom); + set_text(NULL, 0, 0, AlignLeft, AlignBottom); + set_icon(-1, -1, I_ButtonCenter_7x7); + disable_timeout(); + set_timeout(1000); +} + +void PopupVM::set_callback(PopupCallback callback) { + popup_set_callback(popup, callback); +} + +void PopupVM::set_context(void* context) { + popup_set_context(popup, context); +} + +void PopupVM::set_header(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical) { + popup_set_header(popup, text, x, y, horizontal, vertical); +} + +void PopupVM::set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical) { + popup_set_text(popup, text, x, y, horizontal, vertical); +} + +void PopupVM::set_icon(int8_t x, int8_t y, IconName name) { + popup_set_icon(popup, x, y, name); +} + +void PopupVM::set_timeout(uint32_t timeout_in_ms) { + popup_set_timeout(popup, timeout_in_ms); +} + +void PopupVM::enable_timeout() { + popup_enable_timeout(popup); +} + +void PopupVM::disable_timeout() { + popup_enable_timeout(popup); +} diff --git a/lib/app-scened-template/view-modules/popup-vm.h b/lib/app-scened-template/view-modules/popup-vm.h new file mode 100644 index 00000000..e3fb05f8 --- /dev/null +++ b/lib/app-scened-template/view-modules/popup-vm.h @@ -0,0 +1,68 @@ +#pragma once +#include "generic-view-module.h" +#include + +class PopupVM : public GenericViewModule { +public: + PopupVM(); + ~PopupVM() final; + View* get_view() final; + void clean() final; + + /** + * Set popup header text + * @param text - text to be shown + */ + void set_callback(PopupCallback callback); + + /** + * Set popup context + * @param context - context pointer, will be passed to result callback + */ + void set_context(void* context); + + /** + * Set popup header text + * If text is null, popup header will not be rendered + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ + void set_header(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical); + + /** + * Set popup text + * If text is null, popup text will not be rendered + * @param text - text to be shown, can be multiline + * @param x, y - text position + * @param horizontal, vertical - text aligment + */ + void set_text(const char* text, uint8_t x, uint8_t y, Align horizontal, Align vertical); + + /** + * Set popup icon + * If icon position is negative, popup icon will not be rendered + * @param x, y - icon position + * @param name - icon to be shown + */ + void set_icon(int8_t x, int8_t y, IconName name); + + /** + * Set popup timeout + * @param timeout_in_ms - popup timeout value in milliseconds + */ + void set_timeout(uint32_t timeout_in_ms); + + /** + * Enable popup timeout + */ + void enable_timeout(); + + /** + * Disable popup timeout + */ + void disable_timeout(); + +private: + Popup* popup; +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-modules/submenu-vm.cpp b/lib/app-scened-template/view-modules/submenu-vm.cpp new file mode 100644 index 00000000..12c283f9 --- /dev/null +++ b/lib/app-scened-template/view-modules/submenu-vm.cpp @@ -0,0 +1,33 @@ +#include "submenu-vm.h" + +SubmenuVM::SubmenuVM() { + submenu = submenu_alloc(); +} + +SubmenuVM::~SubmenuVM() { + submenu_free(submenu); +} + +View* SubmenuVM::get_view() { + return submenu_get_view(submenu); +} + +void SubmenuVM::clean() { + submenu_clean(submenu); +} + +SubmenuItem* SubmenuVM::add_item( + const char* label, + uint32_t index, + SubmenuItemCallback callback, + void* callback_context) { + return submenu_add_item(submenu, label, index, callback, callback_context); +} + +void SubmenuVM::set_selected_item(uint32_t index) { + submenu_set_selected_item(submenu, index); +} + +void SubmenuVM::set_header(const char* header) { + submenu_set_header(submenu, header); +} diff --git a/lib/app-scened-template/view-modules/submenu-vm.h b/lib/app-scened-template/view-modules/submenu-vm.h new file mode 100644 index 00000000..d508d75e --- /dev/null +++ b/lib/app-scened-template/view-modules/submenu-vm.h @@ -0,0 +1,43 @@ +#pragma once +#include "generic-view-module.h" +#include + +class SubmenuVM : public GenericViewModule { +public: + SubmenuVM(); + ~SubmenuVM() final; + View* get_view() final; + void clean() final; + + /** + * @brief Add item to submenu + * + * @param label - menu item label + * @param index - menu item index, used for callback, may be the same with other items + * @param callback - menu item callback + * @param callback_context - menu item callback context + * @return SubmenuItem instance that can be used to modify or delete that item + */ + SubmenuItem* add_item( + const char* label, + uint32_t index, + SubmenuItemCallback callback, + void* callback_context); + + /** + * @brief Set submenu item selector + * + * @param index index of the item to be selected + */ + void set_selected_item(uint32_t index); + + /** + * @brief Set optional header for submenu + * + * @param header header to set + */ + void set_header(const char* header); + +private: + Submenu* submenu; +}; \ No newline at end of file diff --git a/lib/app-scened-template/view-modules/text-input-vm.cpp b/lib/app-scened-template/view-modules/text-input-vm.cpp new file mode 100644 index 00000000..027b510b --- /dev/null +++ b/lib/app-scened-template/view-modules/text-input-vm.cpp @@ -0,0 +1,30 @@ +#include "text-input-vm.h" + +TextInputVM::TextInputVM() { + text_input = text_input_alloc(); +} + +TextInputVM::~TextInputVM() { + text_input_free(text_input); +} + +View* TextInputVM::get_view() { + return text_input_get_view(text_input); +} + +void TextInputVM::clean() { + set_result_callback(NULL, NULL, NULL, 0); + set_header_text(""); +} + +void TextInputVM::set_result_callback( + TextInputCallback callback, + void* callback_context, + char* text, + uint8_t max_text_length) { + text_input_set_result_callback(text_input, callback, callback_context, text, max_text_length); +} + +void TextInputVM::set_header_text(const char* text) { + text_input_set_header_text(text_input, text); +} diff --git a/lib/app-scened-template/view-modules/text-input-vm.h b/lib/app-scened-template/view-modules/text-input-vm.h new file mode 100644 index 00000000..cf256747 --- /dev/null +++ b/lib/app-scened-template/view-modules/text-input-vm.h @@ -0,0 +1,35 @@ +#pragma once +#include "generic-view-module.h" +#include + +class TextInputVM : public GenericViewModule { +public: + TextInputVM(); + ~TextInputVM() final; + View* get_view() final; + void clean() final; + + /** + * @brief Set text input result callback + * + * @param callback - callback fn + * @param callback_context - callback context + * @param text - text buffer to use + * @param max_text_length - text buffer length + */ + void set_result_callback( + TextInputCallback callback, + void* callback_context, + char* text, + uint8_t max_text_length); + + /** + * @brief Set text input header text + * + * @param text - text to be shown + */ + void set_header_text(const char* text); + +private: + TextInput* text_input; +}; \ No newline at end of file diff --git a/lib/lib.mk b/lib/lib.mk index 4276b24c..eb19ebdb 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -94,4 +94,9 @@ C_SOURCES += $(wildcard $(LIB_DIR)/irda/*/*.c) #args lib CFLAGS += -I$(LIB_DIR)/args -C_SOURCES += $(wildcard $(LIB_DIR)/args/*.c) \ No newline at end of file +C_SOURCES += $(wildcard $(LIB_DIR)/args/*.c) + +#scened app template lib +CFLAGS += -I$(LIB_DIR)/app-scened-template +CPP_SOURCES += $(wildcard $(LIB_DIR)/app-scened-template/*.cpp) +CPP_SOURCES += $(wildcard $(LIB_DIR)/app-scened-template/*/*.cpp) \ No newline at end of file