diff --git a/CHANGELOG.md b/CHANGELOG.md index c00dcc18..26e29a80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ Changes to this project will be logged in this file. This project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## 3.0.0 + +### Added + +- A class-based view API + ## 2.3.0 Robodash 2.3.0 improves the selector UI. diff --git a/Makefile b/Makefile index e441bdbc..f059d7f4 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ INCDIR=$(ROOT)/include WARNFLAGS+= EXTRA_CFLAGS= -EXTRA_CXXFLAGS= +EXTRA_CXXFLAGS=-Wno-deprecated-enum-enum-conversion # Set to 1 to enable hot/cold linking USE_PACKAGE:=1 @@ -26,7 +26,7 @@ EXCLUDE_COLD_LIBRARIES:= # Set this to 1 to add additional rules to compile your project as a PROS library template IS_LIBRARY:=1 LIBNAME:=robodash -VERSION:=2.3.0 +VERSION:=3.0.0 # EXCLUDE_SRC_FROM_LIB= $(SRCDIR)/unpublishedfile.c # this line excludes opcontrol.c and similar files diff --git a/docs/Doxyfile b/docs/Doxyfile index 726323f7..6694426a 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = Robodash # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2.3.0 +PROJECT_NUMBER = 3.0.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/docs/source/api/core.md b/docs/source/api/core.md deleted file mode 100644 index ce64a65a..00000000 --- a/docs/source/api/core.md +++ /dev/null @@ -1,5 +0,0 @@ -# Core - -```{doxygengroup} core -:members: -``` diff --git a/docs/source/api/index.md b/docs/source/api/index.md index e0c6a1de..b0b9dc6f 100644 --- a/docs/source/api/index.md +++ b/docs/source/api/index.md @@ -1,6 +1,6 @@ # API Reference ```{toctree} -core.md -views/index.md +view.md +widgets/index.md ``` diff --git a/docs/source/api/view.md b/docs/source/api/view.md new file mode 100644 index 00000000..d376332d --- /dev/null +++ b/docs/source/api/view.md @@ -0,0 +1,9 @@ +# View API + +```{doxygenenum} rd::ViewFlag +:outline: +``` + +```{doxygenclass} rd::View +:members: +``` diff --git a/docs/source/api/views/console.md b/docs/source/api/views/console.md deleted file mode 100644 index b3f0020e..00000000 --- a/docs/source/api/views/console.md +++ /dev/null @@ -1,5 +0,0 @@ -# Console - -```{doxygengroup} console -:members: -``` diff --git a/docs/source/api/views/image.md b/docs/source/api/views/image.md deleted file mode 100644 index 8686ab81..00000000 --- a/docs/source/api/views/image.md +++ /dev/null @@ -1,5 +0,0 @@ -# Image - -```{doxygengroup} image -:members: -``` diff --git a/docs/source/api/views/index.md b/docs/source/api/views/index.md deleted file mode 100644 index d689c5ee..00000000 --- a/docs/source/api/views/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Views - -Included robodash views. - -```{toctree} -:caption: Views - -selector.md -image.md -console.md -``` diff --git a/docs/source/api/views/selector.md b/docs/source/api/views/selector.md deleted file mode 100644 index 6ddb7733..00000000 --- a/docs/source/api/views/selector.md +++ /dev/null @@ -1,5 +0,0 @@ -# Selector - -```{doxygengroup} selector -:members: -``` diff --git a/docs/source/api/widgets/console.md b/docs/source/api/widgets/console.md new file mode 100644 index 00000000..c4ddd844 --- /dev/null +++ b/docs/source/api/widgets/console.md @@ -0,0 +1,5 @@ +# Console + +```{doxygenclass} rd::Console +:members: +``` diff --git a/docs/source/api/widgets/image.md b/docs/source/api/widgets/image.md new file mode 100644 index 00000000..68d8f976 --- /dev/null +++ b/docs/source/api/widgets/image.md @@ -0,0 +1,5 @@ +# Image + +```{doxygenclass} rd::Image +:members: +``` diff --git a/docs/source/api/widgets/index.md b/docs/source/api/widgets/index.md new file mode 100644 index 00000000..2f6f87d4 --- /dev/null +++ b/docs/source/api/widgets/index.md @@ -0,0 +1,11 @@ +# Widgets + +Included robodash widgets. + +```{toctree} +:caption: Widgets + +selector.md +image.md +console.md +``` diff --git a/docs/source/api/widgets/selector.md b/docs/source/api/widgets/selector.md new file mode 100644 index 00000000..70447c0a --- /dev/null +++ b/docs/source/api/widgets/selector.md @@ -0,0 +1,5 @@ +# Selector + +```{doxygenclass} rd::Selector +:members: +``` diff --git a/docs/source/guides/usage/views.md b/docs/source/guides/usage/views.md index 5c1675ac..3d3a797b 100644 --- a/docs/source/guides/usage/views.md +++ b/docs/source/guides/usage/views.md @@ -27,4 +27,4 @@ lv_label_set_text(label, "example"); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); ``` -[Views API Reference](../../api/core.md) page. +[Views API Reference](../../api/view.md) page. diff --git a/docs/source/guides/usage/widgets.md b/docs/source/guides/usage/widgets.md index 9b5a9899..9c5f4a4d 100644 --- a/docs/source/guides/usage/widgets.md +++ b/docs/source/guides/usage/widgets.md @@ -4,7 +4,7 @@ Robodash provides a suite of simple, commonly used GUI widgets. ## Selector -The [Selector Widget](../../api/views/selector.md) is a function selector +The [Selector Widget](../../api/widgets/selector.md) is a function selector designed for managing autonomous runs. To use the selector we can construct it in the global scope of our `main.cpp` @@ -43,7 +43,7 @@ preserved next time the program is run. ## Console -The [Console Widget](../../api/views/console.md) provides a text display for +The [Console Widget](../../api/widgets/console.md) provides a text display for quickly and easily displaying information for debugging. To create a console, we can construct it in our `main.cpp` file's global scope. @@ -62,7 +62,7 @@ console.printf("The robot's heading is %f\n", some_imu.get_heading()); ## Image -The [Image Widget](../../api/views/image.md) provides a way to display LVGL +The [Image Widget](../../api/widgets/image.md) provides a way to display LVGL images to the LCD. Images displayed must be converted with the diff --git a/include/robodash/alert.hpp b/include/robodash/alert.hpp new file mode 100644 index 00000000..55e77a09 --- /dev/null +++ b/include/robodash/alert.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "robodash/view.hpp" +#include + +namespace rd { + +/** + * Dispatch an alert + * + * @param message Message to display + * @param title Title of alert + */ +void alert(std::string message, std::string title = ""); + +/** + * Dispatch an alert that links back to a view + * + * @param message Message to display + * @param view View to link back to + */ +void alert(std::string message, View &view); + +} // namespace rd diff --git a/include/robodash/api.h b/include/robodash/api.hpp similarity index 70% rename from include/robodash/api.h rename to include/robodash/api.hpp index ccf5ee5d..dceec3ba 100644 --- a/include/robodash/api.h +++ b/include/robodash/api.hpp @@ -1,15 +1,8 @@ -/** - * @file api.h - * @brief API entrypoint for robodash - * - * Includes all necessary functions to use robodash. - */ - #pragma once #define ROBODASH -#define RD_VERSION_MAJOR 2 -#define RD_VERSION_MINOR 3 +#define RD_VERSION_MAJOR 3 +#define RD_VERSION_MINOR 0 #define RD_VERSION_PATCH 0 #include "liblvgl/lvgl.h" @@ -34,10 +27,10 @@ // ============================ Include Library ============================ // -#include "core.h" - -#ifdef __cplusplus -#include "views/console.hpp" -#include "views/image.hpp" -#include "views/selector.hpp" -#endif \ No newline at end of file +#include "alert.hpp" +#include "util/kv_store.hpp" +#include "view.hpp" +#include "widgets/console.hpp" +#include "widgets/image.hpp" +#include "widgets/selector.hpp" +#include "widgets/settings.hpp" \ No newline at end of file diff --git a/include/robodash/apix.h b/include/robodash/apix.h deleted file mode 100644 index 5bcc53a1..00000000 --- a/include/robodash/apix.h +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @file apix.h - * @internal - * - * Extended API header for robodash. - * - * Includes additional declarations for fonts, styles, and more. Automatically - * includes the main API header file as well. - * - * Parts of the library may appear here if they are not useful for the - * average end-user, and could pollute tools like intellisense with extern - * variables. - */ - -#pragma once - -#include "api.h" -#include "impl/assets.h" -#include "impl/filesystem.h" -#include "impl/styles.h" \ No newline at end of file diff --git a/include/robodash/core.h b/include/robodash/core.h deleted file mode 100644 index 0a1f3f11..00000000 --- a/include/robodash/core.h +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @file core.h - * @brief Robodash core header - * @ingroup core - */ - -#pragma once - -#include "liblvgl/lvgl.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @defgroup core Core - * @brief The view management system. - * - * The LVGL view management system to enable compatibility between templates that provide LVGL GUIs. - * Knowledge of LVGL is required to use this API. - */ - -/// @addtogroup core -/// @{ - -/** - * @brief Animation state - */ -typedef enum rd_anim_state { RD_ANIM_ON, RD_ANIM_OFF } rd_anim_state_t; - -/** - * @brief Robodash view structure - */ -typedef struct rd_view { - const char *name; - lv_obj_t *obj; - lv_obj_t *_list_btn; - rd_anim_state_t anims; -} rd_view_t; - -/** - * @brief Create a view - * - * @param name Name of the view - * @return A pointer to a view object - */ -rd_view_t *rd_view_create(const char *name); - -/** - * @brief Set a view to the currently active view - * - * @param view View to focus - */ -void rd_view_focus(rd_view_t *view); - -/** - * @brief Delete a view - * @warning Deleting a view will free the memory the view occupied, but will not set any variables - * pointing to said view `NULL`. - * - * @param view View to delete - */ -void rd_view_del(rd_view_t *view); - -/** - * @brief Get the view's lvgl object - * - * @param view View to query - */ -lv_obj_t *rd_view_obj(rd_view_t *view); - -/** - * @brief Push an alert - * - * Pushes an alert to the screen, regardless of which view is active. - * - * @param view View to link back to - * @param msg Message to display - */ -void rd_view_alert(rd_view_t *view, const char *msg); - -/** - * @brief Enable or disable animations for a view - * - * @param view View to modify - * @param state Animation state - */ -void rd_view_set_anims(rd_view_t *view, rd_anim_state_t state); - -/** - * @brief Get the animation state for a view - * - * @param view View to query - * @return The animation state - */ -rd_anim_state_t rd_view_get_anims(rd_view_t *view); - -/// @} - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/include/robodash/impl/assets.h b/include/robodash/detail/assets.h similarity index 56% rename from include/robodash/impl/assets.h rename to include/robodash/detail/assets.h index ebbd48c5..68198355 100644 --- a/include/robodash/impl/assets.h +++ b/include/robodash/detail/assets.h @@ -1,11 +1,5 @@ -/** - * @file assets.h - * @brief Images and fonts used by robodash - * @internal - */ - #pragma once -#include "robodash/apix.h" +#include "liblvgl/lvgl.h" #ifdef __cplusplus extern "C" { diff --git a/include/robodash/detail/gui.hpp b/include/robodash/detail/gui.hpp new file mode 100644 index 00000000..a8244563 --- /dev/null +++ b/include/robodash/detail/gui.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "liblvgl/lvgl.h" +#include "robodash/view.hpp" + +extern lv_obj_t *view_cont; +extern lv_obj_t *view_list; +extern lv_obj_t *alert_cont; + +extern lv_obj_t *anim_label; +extern lv_obj_t *shade; + +extern lv_anim_t anim_shade_show; +extern lv_anim_t anim_shade_hide; + +extern rd::View *current_view; + +void view_focus_cb(lv_event_t *event); +void close_cb(lv_event_t *event); + +void hide_menu(); +void show_menu(); + +void hide_shade(); +void show_shade(); + +bool valid_view(rd::View *view); +void initialize(); diff --git a/include/robodash/impl/styles.h b/include/robodash/detail/styles.h similarity index 85% rename from include/robodash/impl/styles.h rename to include/robodash/detail/styles.h index f317f810..b8d0507d 100644 --- a/include/robodash/impl/styles.h +++ b/include/robodash/detail/styles.h @@ -1,17 +1,11 @@ -/** - * @file styles.h - * @brief Styles used by robodash - * @internal - */ - #pragma once -#include "robodash/apix.h" +#include "liblvgl/lvgl.h" #ifdef __cplusplus extern "C" { #endif -extern void _init_styles(); +void create_styles(); // ========================== Animation Callbacks ========================== // @@ -34,16 +28,12 @@ extern lv_color_t color_bar; extern lv_color_t color_bar_dark; extern lv_color_t color_bar_outline; -extern void _init_colors(); - // ============================= Miscellaneous ============================= // extern lv_style_t style_bg; extern lv_style_t style_transp; extern lv_style_t style_alert; -extern void _init_style_misc(); - // ================================= Lists ================================= // extern lv_style_t style_list; @@ -51,8 +41,6 @@ extern lv_style_t style_list_btn; extern lv_style_t style_list_btn_pr; extern lv_style_t style_list_btn_ch; -extern void _init_style_list(); - // ================================= Buttons ================================= // extern lv_style_t style_btn; @@ -61,8 +49,6 @@ extern lv_style_t style_btn_primary_pr; extern lv_style_t style_btn_outline; extern lv_style_t style_btn_outline_pr; -extern void _init_style_btn(); - // ================================== Text ================================== // extern lv_style_t style_text_mono; @@ -71,8 +57,6 @@ extern lv_style_t style_text_medium; extern lv_style_t style_text_large; extern lv_style_t style_text_centered; -extern void _init_style_text(); - // ================================== Core ================================== // extern lv_style_t style_core_button; @@ -82,8 +66,6 @@ extern lv_style_t style_core_list_btn; extern lv_style_t style_core_bg; extern lv_style_t style_core_shade; -extern void _init_style_core(); - #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/include/robodash/impl/filesystem.h b/include/robodash/impl/filesystem.h deleted file mode 100644 index d93651b2..00000000 --- a/include/robodash/impl/filesystem.h +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @file filesystem.h - * @brief LVGL filesystem driver for images - * @internal - */ - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -void _init_fs(); - -#ifdef __cplusplus -} -#endif diff --git a/include/robodash/util/kv_store.hpp b/include/robodash/util/kv_store.hpp new file mode 100644 index 00000000..af2c9c17 --- /dev/null +++ b/include/robodash/util/kv_store.hpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include + +namespace rd { +namespace util { + +/** + * Variant of possible value types. + * + * Allowed types: `bool`, `char`, `double`, `int`, `std::string` + */ +using VType = std::variant; + +/** + * Proprietary lightweight persistent key-value storage utility. Requires an SD card. + */ +class KVStore { + public: + /** + * Create a new KVStore + * + * @param file + */ + KVStore(std::string file); + + /** + * Set a value + * + * @param key Key name + * @param value Value + */ + void set(std::string key, VType value); + + /** + * Get a value + * + * @param key Key name + * @return Optional value of varying type + */ + std::optional get(std::string key); + + /** + * Get a value of a certain type + * + * @tparam Type of value + * @param key Key name + * @return Optional value of type T + */ + template std::optional get(std::string key) { + static_assert(ValidVType::value, "Invalid type"); + std::map::iterator iter = data.find(key); + if (iter != data.end()) { + T *value = std::get_if(&iter->second); + if (value != nullptr) return *value; + } + return std::nullopt; + } + + /** + * Ensure that a value is initialized + * + * Gets the value of the given type at the provided key. If the value is not set or not of the + * same type, the provided default value will be set. + * + * @tparam Type of value + * @param key Key name + * @param default_value Value to set if not found + * @return Value of type T + */ + template T ensure(std::string key, T default_value) { + static_assert(ValidVType::value, "Invalid type"); + std::map::iterator iter = data.find(key); + if (iter != data.end()) { + T *value = std::get_if(&iter->second); + if (value != nullptr) return *value; + } + set(key, default_value); + return default_value; + } + + private: + void load(); + std::optional> parse_line(std::string line); + std::optional parse_value(std::string value); + + template + using ValidVType = typename std::disjunction< + std::is_same, std::is_same, std::is_same, + std::is_same, std::is_same>::type; + + std::string file_path; + std::map data; +}; + +} // namespace util +} // namespace rd \ No newline at end of file diff --git a/include/robodash/view.hpp b/include/robodash/view.hpp new file mode 100644 index 00000000..1723c14a --- /dev/null +++ b/include/robodash/view.hpp @@ -0,0 +1,95 @@ +#pragma once +#include "liblvgl/lvgl.h" +#include +#include + +namespace rd { + +/** + * Flags to alter the behavior of a view + */ +enum class ViewFlag { + NoAnimation, +}; + +/** + * The view class + */ +class View { + public: + /** + * Construct a view + * + * @param name Name to display in the sidebar + */ + View(std::string name); + + /** + * Destruct this view + */ + ~View(); + + /** + * Make this view the active view + */ + void focus(); + + /** + * Add a flag to this view + * + * @param flag Flag to add + */ + void add_flag(ViewFlag flag); + + /** + * Check if this view has a flag + * + * @param flag Flag to check for + * @return bool + */ + bool has_flag(ViewFlag flag); + + /** + * Remove a flag from this view + * + * @param flag Flag to remove + */ + void remove_flag(ViewFlag flag); + + /** + * Get this view's name + * + * @return std::string + */ + std::string get_name(); + + /** + * Get the LVGL object for this view + * + * @return lv_obj_t* + */ + lv_obj_t *get_lv_obj(); + + /** + * Enables casting to an lv_obj_t* for ease of use + * + * @return lv_obj_t* + */ + operator lv_obj_t *(); + + protected: + /** + * Refresh this view's behavior based on the current flags. + * + * Called when flags are modified. + */ + void refresh(); + + private: + lv_obj_t *btn_obj; + lv_obj_t *scr_obj; + std::vector flags; + std::string name; +}; + +} // namespace rd \ No newline at end of file diff --git a/include/robodash/views/console.hpp b/include/robodash/widgets/console.hpp similarity index 55% rename from include/robodash/views/console.hpp rename to include/robodash/widgets/console.hpp index b5f94692..3d6185d3 100644 --- a/include/robodash/views/console.hpp +++ b/include/robodash/widgets/console.hpp @@ -1,35 +1,20 @@ -/** - * @file console.hpp - * @brief Robodash Console - * @ingroup console - */ - #pragma once -#include "robodash/api.h" +#include "liblvgl/lvgl.h" +#include "robodash/view.hpp" +#include "robodash/widgets/widget.hpp" #include #include namespace rd { /** - * @defgroup console Console - * @brief A console for debugging - * @image html console.png - * * A GUI console for debugging. Emulates a standard console output. + * + * @image html console.png */ - -/** - * @brief Console class - * @ingroup console - */ -class Console { - /// @addtogroup console - /// @{ - - /// @name Console Functions +class Console : public Widget { private: - rd_view_t *view; + rd::View view; lv_obj_t *output; lv_obj_t *output_cont; @@ -37,51 +22,48 @@ class Console { public: /** - * @brief Create a new Console + * Create a new Console * * @param name Name to display on screen */ Console(std::string name = "Console"); /** - * @brief Clear all console lines + * Clear all console lines */ void clear(); /** - * @brief Print to the console + * Print to the console * * @param str String to print to console */ void print(std::string str); /** - * @brief Print to the console with a newline + * Print to the console with a newline * * @param str String to print to console */ void println(std::string str); /** - * @brief Print a formatted string to the console + * Print a formatted string to the console * * @tparam Params * @param fmt Format string * @param args Args for format string */ - template - void printf(std::string fmt, Params... args) { + template void printf(std::string fmt, Params... args) { char fstr[sizeof(fmt) + sizeof...(args)]; sprintf(fstr, fmt.c_str(), args...); print(fstr); } /** - * @brief Set this view to the active view + * Set this view to the active view */ void focus(); - - /// @} }; } // namespace rd \ No newline at end of file diff --git a/include/robodash/views/image.hpp b/include/robodash/widgets/image.hpp similarity index 70% rename from include/robodash/views/image.hpp rename to include/robodash/widgets/image.hpp index cd82d7e2..66d3e5c3 100644 --- a/include/robodash/views/image.hpp +++ b/include/robodash/widgets/image.hpp @@ -1,18 +1,16 @@ -/** - * @file image.hpp - * @brief Robodash Image - * @ingroup image - */ - #pragma once -#include "robodash/api.h" +#include "liblvgl/lvgl.h" +#include "robodash/view.hpp" +#include "robodash/widgets/widget.hpp" #include namespace rd { /** - * @defgroup image Image - * @brief An image display + * An image display + * + * Displays still images from an SD card or C array. + * * @note All images must be converted using LVGL's online image converter * tool. (https://lvgl.io/tools/imageconverter) * @warning Images should be converted into an indexed color format for @@ -20,25 +18,14 @@ namespace rd { * color images. * * @image html image.png - * - * Displays still images from an SD card or C array. - */ - -/** - * @brief Image class - * @ingroup image */ -class Image { - /// @addtogroup image - /// @{ - - /// @name Image Functions +class Image : public Widget { private: - rd_view_t *view; + rd::View view; public: /** - * @brief Create a new Image + * Create a new Image * * @param path File path to the binary-formatted image on SD card * @param name Name to display on screen @@ -46,7 +33,7 @@ class Image { Image(std::string path, std::string name = "Image"); /** - * @brief Create a new Image + * Create a new Image * * @param image_dsc Pointer to LVGL image descriptor object * @param name Name to display on screen @@ -54,7 +41,7 @@ class Image { Image(lv_img_dsc_t *image_dsc, std::string name = "Image"); /** - * @brief Create a new Image + * Create a new Image * * @param image_dsc Pointer to constant LVGL image descriptor object * @param name Name to display on screen @@ -62,11 +49,9 @@ class Image { Image(const lv_img_dsc_t *image_dsc, std::string name = "Image"); /** - * @brief Set this view to the active view + * Set this view to the active view */ void focus(); - - /// @} }; } // namespace rd diff --git a/include/robodash/views/selector.hpp b/include/robodash/widgets/selector.hpp similarity index 57% rename from include/robodash/views/selector.hpp rename to include/robodash/widgets/selector.hpp index dfa87286..2f21fa75 100644 --- a/include/robodash/views/selector.hpp +++ b/include/robodash/widgets/selector.hpp @@ -1,11 +1,7 @@ -/** - * @file selector.hpp - * @brief Robodash Selector - * @ingroup selector - */ - #pragma once -#include "robodash/api.h" +#include "liblvgl/lvgl.h" +#include "robodash/view.hpp" +#include "robodash/widgets/widget.hpp" #include #include #include @@ -14,92 +10,86 @@ namespace rd { /** - * @defgroup selector Selector - * @brief A function selector - * @image html selector.png + * An autonomous function selector * * A function selector for easily managing autonomous routines. If available, automatically saves * the current configuration to an SD card and loads it on the next run. Also supports displaying * images from the SD card. + * + * @image html selector.png */ - -/** - * @brief Selector class - * @ingroup selector - */ -class Selector { - /// @addtogroup selector - /// @{ +class Selector : public Widget { public: - /// @name Selector Typedefs - typedef std::function routine_action_t; + using RoutineAction = std::function; - typedef struct routine { + struct Routine { std::string name; - routine_action_t action; - std::string img = ""; + RoutineAction action; + std::string image = ""; int color_hue = -1; - } routine_t; + }; - typedef std::function)> select_action_t; - - /// @name Selector Functions + using SelectAction = std::function)>; /** - * @brief Create autonomous selector + * Create autonomous selector + * * @param name Name of the autonomous selector * @param autons Vector of autonomous rotuines */ - Selector(std::string name, std::vector autons); + Selector(std::string name, std::vector autons); /** - * @brief Create autonomous selector + * Create autonomous selector + * * @param autons Vector of autonomous rotuines */ - Selector(std::vector autons); + Selector(std::vector autons); /** - * @brief Run selected auton + * Run selected auton */ void run_auton(); /** * Get the selected auton + * * @return Selected auton */ - std::optional get_auton(); + std::optional get_auton(); /** - * @brief Add a selection callback + * Add a selection callback + * * @param callback The callback function */ - void on_select(select_action_t callback); + void on_select(SelectAction callback); /** - * @brief Select the next auton in the list - * @param wrap_around Whether to wrap around to the beginning once the last auton is reached + * Select the next auton in the list * * Selects the next auton in the list for use with physical buttons such as limit switches. + * + * @param wrap_around Whether to wrap around to the beginning once the last auton is reached */ void next_auton(bool wrap_around = true); /** - * @brief Select the previous auton in the list - * @param wrap_around Whether to wrap around to the end once the first auton is reached + * Select the previous auton in the list * * Selects the previous auton in the list for use with physical buttons such as limit switches. + * + * @param wrap_around Whether to wrap around to the end once the first auton is reached */ void prev_auton(bool wrap_around = true); /** - * @brief Set this view to the active view + * Set this view to the active view */ void focus(); - /// @} - private: - rd_view_t *view; + rd::View view; lv_obj_t *routine_list; lv_obj_t *selected_cont; @@ -107,12 +97,9 @@ class Selector { lv_obj_t *selected_img; std::string name; - std::vector routines; - std::vector select_callbacks; - rd::Selector::routine_t *selected_routine; - - void sd_save(); - void sd_load(); + std::vector routines; + std::vector select_callbacks; + rd::Selector::Routine *selected_routine; void run_callbacks(); diff --git a/include/robodash/widgets/settings.hpp b/include/robodash/widgets/settings.hpp new file mode 100644 index 00000000..6b3f33b1 --- /dev/null +++ b/include/robodash/widgets/settings.hpp @@ -0,0 +1,138 @@ +#pragma once +#include "robodash/view.hpp" +#include +#include +#include + +namespace rd { + +/** + * Representation of a setting value + * + * @tparam T + */ +template class SettingValue { + friend class Settings; + + public: + // Delete the default constructor + SettingValue() = delete; + + // Delete the copy constructor (should only be passed by reference) + SettingValue(const SettingValue &) = delete; + + /** + * Get the value of this setting + * + * @return T + */ + T get() { return *value; } + + /** + * Get the pointer for this setting's value + * + * @return std::shared_ptr + */ + std::shared_ptr ptr() { return value; } + + /** + * Register a callback to run when the setting changes + * + * @param callback + */ + void on_change(std::function callback) { callbacks.push_back(callback); } + + /** + * Get the value of this setting through casting + * + * @return T + */ + T operator T() { return *value; } + + private: + SettingValue(std::shared_ptr value) : value(value) {} + + std::shared_ptr value; + std::vector> callbacks; +}; + +/** + * A settings menu widget to customize robot behavior on the fly + * + * A settings menu for easily managing robot settings. If available, automatically saves + * the current configuration to an SD card and loads it on the next run. + */ +class Settings { + public: + /** + * Create a new settings widget + * + * @param name + */ + Settings(std::string name = "Settings"); + + /** + * Create a button that runs a function when pressed + * + * @param key Name of the button + * @param callback Callback function to run when the button is pressed + */ + void button(std::string key, std::function callback); + + /** + * Declare a toggle setting + * + * @param key Name of the toggle + * @param default_value Default value of the toggle + * @param callback Callback function to run when the toggle changes + */ + SettingValue &toggle(std::string key, bool default_value); + + /** + * Declare a dropdown setting + * + * @param key Name of the dropdown + * @param values Values for the dropdown + * @param default_value Default value of the dropdown + * @param callback Callback function to run when the dropdown changes + */ + SettingValue & + dropdown(std::string key, std::vector values, std::string default_value); + + /** + * Declare a slider setting + * + * @param key Name of the slider + * @param min Minimum value of the slider + * @param max Maximum value of the slider + * @param step Amount to increment the slider by + * @param default_value Default value of the slider + * @param callback Callback function to run when the slider changes + */ + SettingValue & + slider(std::string key, double min, double max, double step, double default_value); + + /** + * Declare an increment setting + * + * @param key Name of the increment + * @param min Minimum value of the increment + * @param max Maximum value of the increment + * @param default_value Default value of the increment + * @param callback Callback function to run when the increment changes + */ + SettingValue &increment(std::string key, int min, int max, int default_value); + + void focus(); + + private: + rd::View view; + + lv_obj_t *settings_cont; + + static void toggle_cb(lv_event_t *event); + + lv_obj_t *create_setting_cont(std::string key); +}; + +} // namespace rd diff --git a/include/robodash/widgets/widget.hpp b/include/robodash/widgets/widget.hpp new file mode 100644 index 00000000..9faddc41 --- /dev/null +++ b/include/robodash/widgets/widget.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace rd { + +/** + * The base Robodash widget + * + * This is an abstract class that all Robodash widgets inherit from to ensure + * consistency. As an end user, you do not need to worry about this class. + */ +class Widget { + public: + virtual void focus() = 0; +}; + +} // namespace rd diff --git a/src/main.cpp b/src/main.cpp index 81e668f9..31879c64 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,5 @@ #include "main.h" -#include "robodash/api.h" +#include "robodash/api.hpp" // ============================= Example autons ============================= // @@ -25,7 +25,7 @@ rd::Console console; void initialize() { // Selector callback example, prints selected auton to the console - selector.on_select([](std::optional routine) { + selector.on_select([](std::optional routine) { if (routine == std::nullopt) { std::cout << "No routine selected" << std::endl; } else { diff --git a/src/robodash/alert.cpp b/src/robodash/alert.cpp new file mode 100644 index 00000000..053f588a --- /dev/null +++ b/src/robodash/alert.cpp @@ -0,0 +1,66 @@ +#include "robodash/alert.hpp" +#include "robodash/detail/gui.hpp" +#include "robodash/detail/styles.h" +#include "robodash/view.hpp" + +// ============================= Alert Callback ============================= // + +void alert_cb(lv_event_t *event) { + rd::View *view = (rd::View *)lv_event_get_user_data(event); + if (valid_view(view)) view->focus(); + + lv_obj_t *alert = lv_event_get_target(event); + lv_obj_del(alert); + + if (lv_obj_get_child_cnt(alert_cont) == 0) { + lv_obj_add_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); + hide_shade(); + } +} + +// ============================ Helper Functions ============================ // + +lv_obj_t *create_alert(std::string message, std::string subtext) { + lv_obj_t *alert = lv_obj_create(alert_cont); + lv_obj_set_width(alert, lv_pct(100)); + lv_obj_set_height(alert, LV_SIZE_CONTENT); + + lv_obj_add_style(alert, &style_alert, 0); + + std::string title = (subtext.empty()) ? "Alert" : "Alert ยท " + subtext; + + lv_obj_t *origin_label = lv_label_create(alert); + lv_obj_align(origin_label, LV_ALIGN_TOP_LEFT, 0, 0); + lv_obj_add_style(origin_label, &style_text_small, 0); + lv_label_set_text(origin_label, title.c_str()); + + lv_obj_t *alert_msg = lv_label_create(alert); + lv_obj_align(alert_msg, LV_ALIGN_TOP_LEFT, 0, 18); + lv_obj_set_width(alert_msg, lv_pct(100)); + lv_obj_add_style(alert_msg, &style_text_medium, 0); + lv_label_set_long_mode(alert_msg, LV_LABEL_LONG_WRAP); + lv_label_set_text(alert_msg, message.c_str()); + + return alert; +} + +// ============================ Alert Functions ============================ // + +void rd::alert(std::string message, std::string title) { + hide_menu(); + show_shade(); + + lv_obj_clear_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); + + create_alert(message, title); +} + +void rd::alert(std::string message, rd::View &view) { + hide_menu(); + show_shade(); + + lv_obj_clear_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); + + lv_obj_t *alert = create_alert(message, view.get_name()); + lv_obj_add_event_cb(alert, alert_cb, LV_EVENT_CLICKED, view); +} diff --git a/src/robodash/filesystem.c b/src/robodash/filesystem.c deleted file mode 100644 index b1932208..00000000 --- a/src/robodash/filesystem.c +++ /dev/null @@ -1,105 +0,0 @@ -/* - Based on the LVGL stdio fs template, with information from - https://www.vexforum.com/t/lvgl-image-not-displaying/63612/14 - - Used for opening images, but write is supported too -*/ - -#include "apix.h" -#include "liblvgl/lvgl.h" -#include - -// ========================= Function Declarations ========================= // - -static void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode) { - LV_UNUSED(drv); - - const char *flags = ""; - - if (mode == LV_FS_MODE_WR) - flags = "wb"; - else if (mode == LV_FS_MODE_RD) - flags = "rb"; - else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) - flags = "rb+"; - - char buf[256]; - sprintf(buf, "%s", path); - - return fopen(buf, flags); -} - -static lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p) { - LV_UNUSED(drv); - fclose((FILE *)file_p); - return LV_FS_RES_OK; -} - -static lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br) { - LV_UNUSED(drv); - *br = fread(buf, 1, btr, (FILE *)file_p); - return (int32_t)(*br) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; -} - -static lv_fs_res_t -fs_write(lv_fs_drv_t *drv, void *file_p, const void *buf, uint32_t btw, uint32_t *bw) { - LV_UNUSED(drv); - *bw = fwrite(buf, 1, btw, (FILE *)file_p); - return (int32_t)(*bw) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; -} - -static lv_fs_res_t fs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos, lv_fs_whence_t whence) { - LV_UNUSED(drv); - int w; - switch (whence) { - case LV_FS_SEEK_SET: - w = SEEK_SET; - break; - case LV_FS_SEEK_CUR: - w = SEEK_CUR; - break; - case LV_FS_SEEK_END: - w = SEEK_END; - break; - default: - return LV_FS_RES_INV_PARAM; - } - - fseek((FILE *)file_p, pos, w); - return LV_FS_RES_OK; -} - -static lv_fs_res_t fs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p) { - LV_UNUSED(drv); - *pos_p = ftell((FILE *)file_p); - return LV_FS_RES_OK; -} - -static void *fs_dir_open(lv_fs_drv_t *drv, const char *path) { return NULL; } - -static lv_fs_res_t fs_dir_read(lv_fs_drv_t *drv, void *rddir_p, char *fn) { - return LV_FS_RES_NOT_IMP; -} - -static lv_fs_res_t fs_dir_close(lv_fs_drv_t *drv, void *rddir_p) { return LV_FS_RES_NOT_IMP; } - -// =============================== Initialize =============================== // - -void _init_fs(void) { - static lv_fs_drv_t fs_drv; - lv_fs_drv_init(&fs_drv); - - fs_drv.letter = 'S'; - fs_drv.open_cb = fs_open; - fs_drv.close_cb = fs_close; - fs_drv.read_cb = fs_read; - fs_drv.write_cb = fs_write; - fs_drv.seek_cb = fs_seek; - fs_drv.tell_cb = fs_tell; - - fs_drv.dir_close_cb = fs_dir_close; - fs_drv.dir_open_cb = fs_dir_open; - fs_drv.dir_read_cb = fs_dir_read; - - lv_fs_drv_register(&fs_drv); -} diff --git a/src/robodash/core.cpp b/src/robodash/gui.cpp similarity index 63% rename from src/robodash/core.cpp rename to src/robodash/gui.cpp index 7b747165..68baef4b 100644 --- a/src/robodash/core.cpp +++ b/src/robodash/gui.cpp @@ -1,4 +1,9 @@ -#include "apix.h" +#include "robodash/detail/gui.hpp" +#include "liblvgl/lvgl.h" +#include "robodash/detail/assets.h" +#include "robodash/detail/styles.h" +#include "robodash/view.hpp" +#include const int view_menu_width = 192; @@ -19,16 +24,16 @@ lv_anim_t anim_sidebar_close; lv_anim_t anim_shade_hide; lv_anim_t anim_shade_show; -rd_view_t *current_view; +rd::View *current_view; // ============================ Helper Functions============================ // -bool valid_view(rd_view_t *view) { +bool valid_view(rd::View *view) { if (view == NULL) return false; for (int i = 0; i < lv_obj_get_child_cnt(view_list); i++) { lv_obj_t *child = lv_obj_get_child(view_list, i); - rd_view_t *reg_view = (rd_view_t *)lv_obj_get_user_data(child); + rd::View *reg_view = (rd::View *)lv_obj_get_user_data(child); if (reg_view == view) return true; } @@ -38,18 +43,13 @@ bool valid_view(rd_view_t *view) { // ============================== UI Callbacks ============================== // void view_focus_cb(lv_event_t *event) { - rd_view_t *view = (rd_view_t *)lv_event_get_user_data(event); - rd_view_focus(view); + rd::View *view = (rd::View *)lv_event_get_user_data(event); + if (valid_view(view)) view->focus(); } void views_btn_cb(lv_event_t *event) { - lv_obj_clear_flag(view_menu, LV_OBJ_FLAG_HIDDEN); - lv_obj_clear_flag(shade, LV_OBJ_FLAG_HIDDEN); - - if (rd_view_get_anims(current_view) == RD_ANIM_ON) { - lv_anim_start(&anim_sidebar_open); - lv_anim_start(&anim_shade_show); - } + show_shade(); + show_menu(); } void close_cb(lv_event_t *event) { @@ -59,39 +59,15 @@ void close_cb(lv_event_t *event) { lv_obj_add_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); - if (rd_view_get_anims(current_view) == RD_ANIM_ON) { - lv_anim_start(&anim_sidebar_close); - lv_anim_start(&anim_shade_hide); - } else { - lv_obj_add_flag(view_menu, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_flag(shade, LV_OBJ_FLAG_HIDDEN); - } + hide_shade(); + hide_menu(); } void alert_btn_cb(lv_event_t *event) { if (!lv_obj_has_flag(alert_cont, LV_OBJ_FLAG_HIDDEN)) return; lv_obj_add_flag(alert_btn, LV_OBJ_FLAG_HIDDEN); lv_obj_clear_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); - lv_obj_clear_flag(shade, LV_OBJ_FLAG_HIDDEN); - if (rd_view_get_anims(current_view) == RD_ANIM_ON) lv_anim_start(&anim_shade_show); -} - -void alert_cb(lv_event_t *event) { - rd_view_t *view = (rd_view_t *)lv_event_get_user_data(event); - rd_view_focus(view); - - lv_obj_t *alert = lv_event_get_target(event); - lv_obj_del(alert); - - if (lv_obj_get_child_cnt(alert_cont) == 0) { - lv_obj_add_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); - - if (rd_view_get_anims(current_view) == RD_ANIM_ON) { - lv_anim_start(&anim_shade_hide); - } else { - lv_obj_add_flag(shade, LV_OBJ_FLAG_HIDDEN); - } - } + show_shade(); } // =========================== UI Initialization =========================== // @@ -223,115 +199,48 @@ void create_anims() { lv_anim_set_values(&anim_shade_show, 0, 144); } -// =============================== Initialize =============================== // - -bool initialized = false; - -void initialize() { - if (initialized) return; - - _init_fs(); - _init_styles(); - - create_ui(); - create_anims(); - - initialized = true; -} - -// =============================== View Class =============================== // - -rd_view_t *rd_view_create(const char *name) { - initialize(); +// ============================== UI Functions ============================== // - rd_view_t *view = (rd_view_t *)malloc(sizeof(rd_view_t)); +void hide_menu() { + if (lv_obj_has_flag(view_menu, LV_OBJ_FLAG_HIDDEN)) return; - view->obj = lv_obj_create(lv_scr_act()); - lv_obj_set_size(view->obj, lv_pct(100), lv_pct(100)); - lv_obj_add_flag(view->obj, LV_OBJ_FLAG_HIDDEN); - lv_obj_add_style(view->obj, &style_bg, 0); - lv_obj_set_parent(view->obj, view_cont); - - view->_list_btn = lv_list_add_btn(view_list, NULL, name); - lv_obj_add_style(view->_list_btn, &style_core_list_btn, 0); - lv_obj_add_style(view->_list_btn, &style_list_btn_pr, LV_STATE_PRESSED); - lv_obj_set_user_data(view->_list_btn, view); - lv_obj_add_event_cb(view->_list_btn, view_focus_cb, LV_EVENT_PRESSED, view); - lv_obj_add_event_cb(view->_list_btn, close_cb, LV_EVENT_PRESSED, NULL); - - view->name = name; - view->anims = RD_ANIM_ON; - - if (!current_view) rd_view_focus(view); - - return view; + if (current_view->has_flag(rd::ViewFlag::NoAnimation)) + lv_obj_add_flag(view_menu, LV_OBJ_FLAG_HIDDEN); + else + lv_anim_start(&anim_sidebar_close); } -void rd_view_del(rd_view_t *view) { - if (!valid_view(view)) return; - - lv_obj_del(view->_list_btn); - lv_obj_del(view->obj); - if (current_view == view) current_view = NULL; - - free(view); +void show_menu() { + if (!lv_obj_has_flag(view_menu, LV_OBJ_FLAG_HIDDEN)) return; + lv_obj_clear_flag(view_menu, LV_OBJ_FLAG_HIDDEN); + if (!current_view->has_flag(rd::ViewFlag::NoAnimation)) lv_anim_start(&anim_sidebar_open); } -void rd_view_set_anims(rd_view_t *view, rd_anim_state_t state) { view->anims = state; } - -rd_anim_state_t rd_view_get_anims(rd_view_t *view) { return view->anims; } - -lv_obj_t *rd_view_obj(rd_view_t *view) { - if (!valid_view(view)) return NULL; +void hide_shade() { + if (lv_obj_has_flag(shade, LV_OBJ_FLAG_HIDDEN)) return; - return view->obj; + if (current_view->has_flag(rd::ViewFlag::NoAnimation)) + lv_obj_add_flag(shade, LV_OBJ_FLAG_HIDDEN); + else + lv_anim_start(&anim_shade_hide); } -void rd_view_focus(rd_view_t *view) { - if (!valid_view(view)) return; - - if (current_view != NULL) lv_obj_add_flag(current_view->obj, LV_OBJ_FLAG_HIDDEN); - current_view = view; - lv_obj_clear_flag(current_view->obj, LV_OBJ_FLAG_HIDDEN); - - if (rd_view_get_anims(current_view) == RD_ANIM_ON) - lv_obj_add_flag(anim_label, LV_OBJ_FLAG_HIDDEN); - else - lv_obj_clear_flag(anim_label, LV_OBJ_FLAG_HIDDEN); +void show_shade() { + if (!lv_obj_has_flag(shade, LV_OBJ_FLAG_HIDDEN)) return; + lv_obj_clear_flag(shade, LV_OBJ_FLAG_HIDDEN); + if (!current_view->has_flag(rd::ViewFlag::NoAnimation)) lv_anim_start(&anim_shade_show); } -void rd_view_alert(rd_view_t *view, const char *msg) { - if (!valid_view(view)) return; +// =============================== Initialize =============================== // - if (!lv_obj_has_flag(view_menu, LV_OBJ_FLAG_HIDDEN)) { - if (rd_view_get_anims(current_view) == RD_ANIM_ON) - lv_anim_start(&anim_sidebar_close); - else - lv_obj_add_flag(view_menu, LV_OBJ_FLAG_HIDDEN); - } +bool initialized = false; - if (lv_obj_has_flag(shade, LV_OBJ_FLAG_HIDDEN)) { - lv_obj_clear_flag(shade, LV_OBJ_FLAG_HIDDEN); - if (rd_view_get_anims(current_view) == RD_ANIM_ON) lv_anim_start(&anim_shade_show); - } +void initialize() { + if (initialized) return; - lv_obj_clear_flag(alert_cont, LV_OBJ_FLAG_HIDDEN); + create_styles(); + create_ui(); + create_anims(); - lv_obj_t *alert = lv_obj_create(alert_cont); - lv_obj_set_width(alert, lv_pct(100)); - lv_obj_set_height(alert, LV_SIZE_CONTENT); - lv_obj_add_event_cb(alert, alert_cb, LV_EVENT_CLICKED, view); - lv_obj_add_style(alert, &style_alert, 0); - - lv_obj_t *origin_label = lv_label_create(alert); - lv_obj_align(origin_label, LV_ALIGN_TOP_LEFT, 0, 0); - lv_obj_add_style(origin_label, &style_text_small, 0); - lv_label_set_text(origin_label, view->name); - - lv_obj_t *alert_msg = lv_label_create(alert); - lv_obj_align(alert_msg, LV_ALIGN_TOP_LEFT, 0, 18); - lv_obj_set_width(alert_msg, lv_pct(100)); - lv_obj_add_style(alert_msg, &style_text_medium, 0); - lv_label_set_long_mode(alert_msg, LV_LABEL_LONG_WRAP); - lv_label_set_text(alert_msg, msg); -} \ No newline at end of file + initialized = true; +} diff --git a/src/robodash/styles/anims.c b/src/robodash/styles/anims.c index 31334319..fda9bff9 100644 --- a/src/robodash/styles/anims.c +++ b/src/robodash/styles/anims.c @@ -1,4 +1,4 @@ -#include "robodash/apix.h" +#include "robodash/detail/styles.h" #include // ========================== Animation Callbacks ========================== // diff --git a/src/robodash/styles/button.c b/src/robodash/styles/button.c index f21dc06b..df65ff86 100644 --- a/src/robodash/styles/button.c +++ b/src/robodash/styles/button.c @@ -1,4 +1,4 @@ -#include "robodash/apix.h" +#include "robodash/detail/styles.h" // ================================= Button ================================= // @@ -8,7 +8,7 @@ lv_style_t style_btn_primary_pr; lv_style_t style_btn_outline; lv_style_t style_btn_outline_pr; -void _init_style_btn() { +void create_btn_styles() { // Base button lv_style_init(&style_btn); lv_style_set_radius(&style_btn, 16); diff --git a/src/robodash/styles/colors.c b/src/robodash/styles/colors.c index 9d639aa9..f34068a7 100644 --- a/src/robodash/styles/colors.c +++ b/src/robodash/styles/colors.c @@ -1,5 +1,4 @@ -#include "liblvgl/misc/lv_color.h" -#include "robodash/apix.h" +#include "robodash/detail/styles.h" // ================================= Colors ================================= // @@ -17,7 +16,7 @@ lv_color_t color_bar; lv_color_t color_bar_dark; lv_color_t color_bar_outline; -void _init_colors() { +void create_colors() { color_bg = lv_color_hsv_to_rgb(hue, 50, 10); color_border = lv_color_hsv_to_rgb(hue, 25, 50); color_shade = lv_color_hsv_to_rgb(hue, 50, 25); diff --git a/src/robodash/styles/core.c b/src/robodash/styles/core.c index d87383df..e860b86d 100644 --- a/src/robodash/styles/core.c +++ b/src/robodash/styles/core.c @@ -1,4 +1,4 @@ -#include "robodash/apix.h" +#include "robodash/detail/styles.h" // ================================== Core ================================== // @@ -9,7 +9,7 @@ lv_style_t style_core_list_btn; lv_style_t style_core_bg; lv_style_t style_core_shade; -void _init_style_core() { +void create_core_styles() { // Sidebar button lv_style_init(&style_core_button); lv_style_set_pad_all(&style_core_button, 8); diff --git a/src/robodash/styles/initialize.c b/src/robodash/styles/initialize.c index af8baa9b..d0bc4617 100644 --- a/src/robodash/styles/initialize.c +++ b/src/robodash/styles/initialize.c @@ -1,10 +1,17 @@ -#include "robodash/apix.h" +#include "robodash/detail/styles.h" -void _init_styles() { - _init_colors(); - _init_style_misc(); - _init_style_text(); - _init_style_btn(); - _init_style_list(); - _init_style_core(); +void create_colors(); +void create_btn_styles(); +void create_core_styles(); +void create_list_styles(); +void create_misc_styles(); +void create_text_styles(); + +void create_styles() { + create_colors(); + create_btn_styles(); + create_core_styles(); + create_list_styles(); + create_misc_styles(); + create_text_styles(); } \ No newline at end of file diff --git a/src/robodash/styles/list.c b/src/robodash/styles/list.c index 1146ca77..be2270b7 100644 --- a/src/robodash/styles/list.c +++ b/src/robodash/styles/list.c @@ -1,4 +1,4 @@ -#include "robodash/apix.h" +#include "robodash/detail/styles.h" // ================================== List ================================== // @@ -7,7 +7,7 @@ lv_style_t style_list_btn; lv_style_t style_list_btn_pr; lv_style_t style_list_btn_ch; -void _init_style_list() { +void create_list_styles() { // List lv_style_init(&style_list); lv_style_set_border_color(&style_list, color_border); diff --git a/src/robodash/styles/misc.c b/src/robodash/styles/misc.c index 98e7ed30..a4b27a89 100644 --- a/src/robodash/styles/misc.c +++ b/src/robodash/styles/misc.c @@ -1,4 +1,4 @@ -#include "robodash/apix.h" +#include "robodash/detail/styles.h" // ============================= Miscellaneous ============================= // @@ -6,7 +6,7 @@ lv_style_t style_bg; lv_style_t style_transp; lv_style_t style_alert; -void _init_style_misc() { +void create_misc_styles() { // Background lv_style_init(&style_bg); lv_style_set_border_width(&style_bg, 0); diff --git a/src/robodash/styles/text.c b/src/robodash/styles/text.c index fbb20468..93c08d3a 100644 --- a/src/robodash/styles/text.c +++ b/src/robodash/styles/text.c @@ -1,4 +1,5 @@ -#include "robodash/apix.h" +#include "robodash/detail/assets.h" +#include "robodash/detail/styles.h" // ================================== Text ================================== // @@ -8,7 +9,7 @@ lv_style_t style_text_medium; lv_style_t style_text_large; lv_style_t style_text_centered; -void _init_style_text() { +void create_text_styles() { // Monospaced text lv_style_init(&style_text_mono); lv_style_set_text_color(&style_text_mono, color_text); diff --git a/src/robodash/util/kv_store.cpp b/src/robodash/util/kv_store.cpp new file mode 100644 index 00000000..26beb4aa --- /dev/null +++ b/src/robodash/util/kv_store.cpp @@ -0,0 +1,125 @@ +#include "robodash/util/kv_store.hpp" +#include +#include +#include +#include +#include +#include + +// ================================ Helpers ================================ // + +bool is_whitespace(char s) { return s == ' ' || s == ' '; }; +bool may_be_integer(char s) { return std::isdigit(s) || s == '-'; } +bool may_be_float(char s) { + return std::isdigit(s) || s == '-' || s == '.' || s == '+' || s == 'e'; +} + +struct ConversionVisitor { + std::string operator()(std::string &v) { return "\"" + v + "\""; } + std::string operator()(int v) { return std::to_string(v); } + std::string operator()(double v) { return std::to_string(v); } + std::string operator()(char v) { return "'" + std::string(1, v) + "'"; } + std::string operator()(bool v) { return (v) ? "true" : "false"; } +}; + +// ========================= KVStore Implementation ========================= // + +rd::util::KVStore::KVStore(std::string file) : file_path(file) { load(); } + +void rd::util::KVStore::set(std::string key, rd::util::VType value) { + std::stringstream buffer; + std::ifstream file_in(file_path); + std::string line; + + while (std::getline(file_in, line)) { + std::optional> parsed_line = parse_line(line); + if (parsed_line.has_value()) { + std::string saved_key = parsed_line.value().first; + if (saved_key == key) continue; + } + + buffer << line << std::endl; + } + + buffer << key << ": " << std::visit(ConversionVisitor(), value) << std::endl; + file_in.close(); + + std::ofstream file_out(file_path); + file_out << buffer.rdbuf(); + file_out.close(); + + load(); +} + +std::optional rd::util::KVStore::get(std::string key) { + std::map::iterator iter = data.find(key); + if (iter == data.end()) return std::nullopt; + return iter->second; +} + +void rd::util::KVStore::load() { + std::ifstream file(file_path); + std::string line; + + while (std::getline(file, line)) { + std::optional> parsed_line = parse_line(line); + if (!parsed_line.has_value()) continue; + std::string key = parsed_line.value().first; + std::string str_value = parsed_line.value().second; + + std::optional value = parse_value(str_value); + if (!value.has_value()) continue; + data.emplace(key, value.value()); + } +} + +std::optional> rd::util::KVStore::parse_line(std::string line) { + std::string::iterator first_char = std::find_if_not(line.begin(), line.end(), is_whitespace); + if (*first_char == '#') return std::nullopt; + + std::string::iterator delimiter = std::find(first_char, line.end(), ':'); + if (delimiter == line.end()) return std::nullopt; + + std::string::iterator value_start = std::find_if_not(delimiter + 1, line.end(), is_whitespace); + + std::string key(first_char, delimiter); + std::string str_value(value_start, line.end()); + + return std::make_pair(key, str_value); +} + +std::optional rd::util::KVStore::parse_value(std::string value) { + // bool + if (value == "true" || value == "false") { + return value == "true"; + } + + // int & float + if (std::find_if_not(value.begin(), value.end(), may_be_integer) == value.end()) { + try { + return std::stoi(value); + } catch (std::invalid_argument err) { + return std::nullopt; + } + } else if (std::find_if_not(value.begin(), value.end(), may_be_float) == value.end()) { + try { + return std::stod(value); + } catch (std::invalid_argument err) { + return std::nullopt; + } + } + + // string + if (value.starts_with('"') && value.ends_with('"')) { + std::string::iterator open_quote = std::find(value.begin(), value.end(), '"'); + std::string::iterator close_quote = std::find(next(open_quote), value.end(), '"'); + return std::string(open_quote + 1, close_quote); + } + + // char + if (value.length() == 3 && value.starts_with('\'') && value.ends_with('\'')) { + return value.at(1); + } + + return std::nullopt; +} diff --git a/src/robodash/view.cpp b/src/robodash/view.cpp new file mode 100644 index 00000000..ee0b5c1e --- /dev/null +++ b/src/robodash/view.cpp @@ -0,0 +1,64 @@ +#include "robodash/view.hpp" +#include "robodash/detail/gui.hpp" +#include "robodash/detail/styles.h" +#include + +rd::View::View(std::string name) { + initialize(); + + this->scr_obj = lv_obj_create(lv_scr_act()); + lv_obj_set_size(this->scr_obj, lv_pct(100), lv_pct(100)); + lv_obj_add_flag(this->scr_obj, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_style(this->scr_obj, &style_bg, 0); + lv_obj_set_parent(this->scr_obj, view_cont); + + this->btn_obj = lv_list_add_btn(view_list, NULL, name.c_str()); + lv_obj_add_style(this->btn_obj, &style_core_list_btn, 0); + lv_obj_add_style(this->btn_obj, &style_list_btn_pr, LV_STATE_PRESSED); + lv_obj_set_user_data(this->btn_obj, this); + lv_obj_add_event_cb(this->btn_obj, view_focus_cb, LV_EVENT_PRESSED, this); + lv_obj_add_event_cb(this->btn_obj, close_cb, LV_EVENT_PRESSED, NULL); + + this->name = name; + + if (!current_view) this->focus(); +} + +rd::View::~View() { + lv_obj_del(this->btn_obj); + lv_obj_del(this->scr_obj); + if (current_view == this) current_view = NULL; +} + +void rd::View::focus() { + if (current_view != NULL) lv_obj_add_flag(*current_view, LV_OBJ_FLAG_HIDDEN); + current_view = this; + lv_obj_clear_flag(this->scr_obj, LV_OBJ_FLAG_HIDDEN); + + if (this->has_flag(ViewFlag::NoAnimation)) + lv_obj_add_flag(anim_label, LV_OBJ_FLAG_HIDDEN); + else + lv_obj_clear_flag(anim_label, LV_OBJ_FLAG_HIDDEN); +} + +void rd::View::add_flag(rd::ViewFlag flag) { + flags.push_back(flag); + refresh(); +} + +bool rd::View::has_flag(rd::ViewFlag flag) { + return std::find(flags.begin(), flags.end(), flag) != flags.end(); +} + +void rd::View::remove_flag(rd::ViewFlag flag) { + flags.erase(std::remove(flags.begin(), flags.end(), flag), flags.end()); + refresh(); +} + +void rd::View::refresh() {} + +std::string rd::View::get_name() { return name; } + +lv_obj_t *rd::View::get_lv_obj() { return scr_obj; } + +rd::View::operator lv_obj_t *() { return scr_obj; } diff --git a/src/robodash/views/console.cpp b/src/robodash/widgets/console.cpp similarity index 78% rename from src/robodash/views/console.cpp rename to src/robodash/widgets/console.cpp index 249c0d43..b79946d4 100644 --- a/src/robodash/views/console.cpp +++ b/src/robodash/widgets/console.cpp @@ -1,14 +1,12 @@ -#include "console.hpp" -#include "robodash/apix.h" +#include "robodash/widgets/console.hpp" +#include "robodash/detail/styles.h" // ============================= Core Functions ============================= // -rd::Console::Console(std::string name) { - this->view = rd_view_create(name.c_str()); +rd::Console::Console(std::string name) : view(name) { + lv_obj_set_style_bg_color(view, color_bg, 0); - lv_obj_set_style_bg_color(view->obj, color_bg, 0); - - this->output_cont = lv_obj_create(view->obj); + this->output_cont = lv_obj_create(view); lv_obj_set_width(output_cont, lv_pct(100)); lv_obj_set_height(output_cont, lv_pct(100)); lv_obj_align(output_cont, LV_ALIGN_CENTER, 0, 0); @@ -38,4 +36,4 @@ void rd::Console::print(std::string str) { void rd::Console::println(std::string str) { this->print(str + "\n"); } -void rd::Console::focus() { rd_view_focus(this->view); } +void rd::Console::focus() { view.focus(); } diff --git a/src/robodash/views/image.cpp b/src/robodash/widgets/image.cpp similarity index 56% rename from src/robodash/views/image.cpp rename to src/robodash/widgets/image.cpp index 3329da85..11a7794c 100644 --- a/src/robodash/views/image.cpp +++ b/src/robodash/widgets/image.cpp @@ -1,27 +1,29 @@ +#include "robodash/widgets/image.hpp" #include "api.h" -#include "robodash/apix.h" +#include "robodash/detail/styles.h" +#include "robodash/view.hpp" // ============================= Core Functions ============================= // -rd::Image::Image(const lv_img_dsc_t *image_dsc, std::string name) { - this->view = rd_view_create(name.c_str()); - lv_obj_t *image = lv_img_create(view->obj); +rd::Image::Image(const lv_img_dsc_t *image_dsc, std::string name) : view(name) { + lv_obj_t *image = lv_img_create(view); lv_img_set_src(image, image_dsc); lv_obj_align(image, LV_ALIGN_CENTER, 0, 0); - rd_view_set_anims(this->view, RD_ANIM_OFF); + + view.add_flag(ViewFlag::NoAnimation); } rd::Image::Image(lv_img_dsc_t *image_dsc, std::string name) : Image(const_cast(image_dsc), name) {} -rd::Image::Image(std::string path, std::string name) { +rd::Image::Image(std::string path, std::string name) : view(name) { if (!pros::usd::is_installed()) return; - this->view = rd_view_create(name.c_str()); - lv_obj_t *image = lv_img_create(view->obj); + lv_obj_t *image = lv_img_create(view); lv_img_set_src(image, ("S:" + path).c_str()); lv_obj_align(image, LV_ALIGN_CENTER, 0, 0); - rd_view_set_anims(this->view, RD_ANIM_OFF); + + view.add_flag(ViewFlag::NoAnimation); } -void rd::Image::focus() { rd_view_focus(this->view); } \ No newline at end of file +void rd::Image::focus() { view.focus(); } diff --git a/src/robodash/views/selector.cpp b/src/robodash/widgets/selector.cpp similarity index 73% rename from src/robodash/views/selector.cpp rename to src/robodash/widgets/selector.cpp index 54f984ee..01d65ddb 100644 --- a/src/robodash/views/selector.cpp +++ b/src/robodash/widgets/selector.cpp @@ -1,101 +1,24 @@ -#include "selector.hpp" +#include "robodash/widgets/selector.hpp" #include "api.h" -#include "robodash/apix.h" -#include "robodash/impl/styles.h" -#include +#include "robodash/detail/styles.h" +#include "robodash/util/kv_store.hpp" -const char *file_name = "/usd/rd_auton.txt"; - -// ============================= SD Card Saving ============================= // - -void rd::Selector::sd_save() { - FILE *save_file; - - // Ensure the file exists - save_file = fopen(file_name, "a"); - fclose(save_file); - - // Open in read mode - save_file = fopen(file_name, "r"); - if (!save_file) return; - - // Get file size - fseek(save_file, 0L, SEEK_END); - int file_size = ftell(save_file); - rewind(save_file); - - char new_text[file_size]; - char line[256]; - char saved_selector[256]; - - new_text[0] = '\0'; // THIS IS VERY IMPORTANT - - // Find and remove keys for our selector - while (fgets(line, 256, save_file)) { - sscanf(line, "%[^:] \n", saved_selector); - if (saved_selector == this->name) continue; - strcat(new_text, line); - } - - fclose(save_file); - save_file = fopen(file_name, "w"); - fputs(new_text, save_file); - - // Write save data - if (selected_routine != nullptr) { - const char *selector_name = this->name.c_str(); - const char *routine_name = selected_routine->name.c_str(); - - char file_data[strlen(selector_name) + strlen(routine_name) + 2]; - sprintf(file_data, "%s: %s\n", selector_name, routine_name); - fputs(file_data, save_file); - } - - fclose(save_file); -} - -void rd::Selector::sd_load() { - FILE *save_file; - save_file = fopen(file_name, "r"); - if (!save_file) return; - - // Read contents - char line[256]; - char saved_selector[256]; - char saved_name[256]; - - while (fgets(line, 256, save_file)) { - sscanf(line, "%[^:]: %[^\n\0]", saved_selector, saved_name); - if (saved_selector == this->name) break; - } - - fclose(save_file); - - // None selected or not our selector - if (strcmp(saved_name, "") == 0 || saved_selector != this->name) { - return; - } - - // Press button for selected auton - for (int id = 0; id < lv_obj_get_child_cnt(routine_list); id++) { - lv_obj_t *list_child = lv_obj_get_child(routine_list, id); - if (list_child == nullptr) continue; - if (strcmp(lv_list_get_btn_text(routine_list, list_child), saved_name) != 0) continue; - lv_event_send(list_child, LV_EVENT_CLICKED, selected_routine); - break; - } -} +const std::string file_path = "/usd/robodash/selector.txt"; // ============================== UI Callbacks ============================== // void rd::Selector::select_cb(lv_event_t *event) { lv_obj_t *obj = lv_event_get_target(event); - rd::Selector::routine_t *routine = (rd::Selector::routine_t *)lv_event_get_user_data(event); + rd::Selector::Routine *routine = (rd::Selector::Routine *)lv_event_get_user_data(event); rd::Selector *selector = (rd::Selector *)lv_obj_get_user_data(obj); if (selector == nullptr) return; selector->selected_routine = routine; - selector->sd_save(); + + if (pros::usd::is_installed()) { + rd::util::KVStore kv_store(file_path); + kv_store.set(selector->name, selector->selected_routine->name); + } selector->run_callbacks(); @@ -112,19 +35,16 @@ void rd::Selector::select_cb(lv_event_t *event) { return; } - const char *routine_name = routine->name.c_str(); - - char label_str[strlen(routine_name) + 20]; - sprintf(label_str, "Selected routine:\n%s", routine_name); - lv_label_set_text(selector->selected_label, label_str); + std::string label_str = "Selected routine:\n" + routine->name; + lv_label_set_text(selector->selected_label, label_str.c_str()); lv_obj_align(selector->selected_label, LV_ALIGN_CENTER, 120, 0); - if (routine->img.empty() || !pros::usd::is_installed()) { + if (routine->image.empty() || !pros::usd::is_installed()) { lv_obj_add_flag(selector->selected_img, LV_OBJ_FLAG_HIDDEN); return; } - lv_img_set_src(selector->selected_img, routine->img.c_str()); + lv_img_set_src(selector->selected_img, routine->image.c_str()); lv_obj_clear_flag(selector->selected_img, LV_OBJ_FLAG_HIDDEN); } @@ -154,24 +74,22 @@ void rd::Selector::down_cb(lv_event_t *event) { // ============================== Constructor ============================== // -rd::Selector::Selector(std::vector autons) : Selector("Auton Selector", autons) {} +rd::Selector::Selector(std::vector autons) : Selector("Auton Selector", autons) {} -rd::Selector::Selector(std::string name, std::vector new_routines) { +rd::Selector::Selector(std::string name, std::vector new_routines) : view(name) { this->name = name; this->selected_routine = nullptr; // ----------------------------- Create UI ----------------------------- // - this->view = rd_view_create(name.c_str()); + lv_obj_set_style_bg_color(view, color_bg, 0); - lv_obj_set_style_bg_color(view->obj, color_bg, 0); - - routine_list = lv_list_create(view->obj); + routine_list = lv_list_create(view); lv_obj_set_size(routine_list, 228, 192); lv_obj_align(routine_list, LV_ALIGN_TOP_LEFT, 8, 40); lv_obj_add_style(routine_list, &style_list, 0); - selected_cont = lv_obj_create(view->obj); + selected_cont = lv_obj_create(view); lv_obj_add_style(selected_cont, &style_transp, 0); lv_obj_set_layout(selected_cont, LV_LAYOUT_FLEX); lv_obj_set_size(selected_cont, 240, 240); @@ -191,7 +109,7 @@ rd::Selector::Selector(std::string name, std::vector new_routines) { lv_obj_add_flag(selected_img, LV_OBJ_FLAG_HIDDEN); // Routine list button cluster - lv_obj_t *list_btns = lv_obj_create(view->obj); + lv_obj_t *list_btns = lv_obj_create(view); lv_obj_add_style(list_btns, &style_transp, 0); lv_obj_set_size(list_btns, 32, 192); lv_obj_align(list_btns, LV_ALIGN_TOP_LEFT, 236, 40); @@ -268,7 +186,7 @@ rd::Selector::Selector(std::string name, std::vector new_routines) { lv_obj_set_style_transform_width(nothing_btn, -8, 0); lv_obj_add_state(nothing_btn, LV_STATE_CHECKED); - lv_obj_t *title = lv_label_create(view->obj); + lv_obj_t *title = lv_label_create(view); lv_label_set_text(title, "Select autonomous routine"); lv_obj_add_style(title, &style_text_large, 0); lv_obj_align(title, LV_ALIGN_TOP_LEFT, 8, 12); @@ -282,15 +200,15 @@ rd::Selector::Selector(std::string name, std::vector new_routines) { // ----------------------------- Add autons ----------------------------- // - for (routine_t routine : new_routines) { - if (!routine.img.empty()) { - routine.img.insert(0, "S:"); + for (Routine routine : new_routines) { + if (!routine.image.empty()) { + routine.image.insert(0, "S:"); } routines.push_back(routine); } - for (routine_t &routine : routines) { + for (Routine &routine : routines) { lv_obj_t *new_btn = lv_list_add_btn(routine_list, NULL, routine.name.c_str()); lv_obj_add_style(new_btn, &style_list_btn, 0); @@ -319,7 +237,23 @@ rd::Selector::Selector(std::string name, std::vector new_routines) { lv_obj_clear_flag(pg_up_btn, LV_OBJ_FLAG_HIDDEN); } - if (pros::usd::is_installed()) sd_load(); + if (pros::usd::is_installed()) { + rd::util::KVStore kv_store(file_path); + std::optional saved_name = kv_store.get(name); + if (!saved_name) return; + + // Press button for selected auton + for (int id = 0; id < lv_obj_get_child_cnt(routine_list); id++) { + lv_obj_t *list_child = lv_obj_get_child(routine_list, id); + if (list_child == nullptr) continue; + if (strcmp( + lv_list_get_btn_text(routine_list, list_child), saved_name.value().c_str() + ) != 0) + continue; + lv_event_send(list_child, LV_EVENT_CLICKED, selected_routine); + break; + } + } } // ============================= Other Methods ============================= // @@ -369,7 +303,7 @@ void rd::Selector::prev_auton(bool wrap_around) { } void rd::Selector::run_callbacks() { - for (select_action_t callback : this->select_callbacks) { + for (SelectAction callback : this->select_callbacks) { if (this->selected_routine == nullptr) { callback(std::nullopt); } else { @@ -383,13 +317,13 @@ void rd::Selector::run_auton() { selected_routine->action(); } -std::optional rd::Selector::get_auton() { +std::optional rd::Selector::get_auton() { if (selected_routine == nullptr) return std::nullopt; return *selected_routine; } -void rd::Selector::on_select(rd::Selector::select_action_t callback) { +void rd::Selector::on_select(rd::Selector::SelectAction callback) { select_callbacks.push_back(callback); } -void rd::Selector::focus() { rd_view_focus(this->view); } \ No newline at end of file +void rd::Selector::focus() { view.focus(); } diff --git a/src/robodash/widgets/settings.cpp b/src/robodash/widgets/settings.cpp new file mode 100644 index 00000000..1c6acd09 --- /dev/null +++ b/src/robodash/widgets/settings.cpp @@ -0,0 +1,119 @@ +#include "robodash/widgets/settings.hpp" +#include "liblvgl/extra/layouts/flex/lv_flex.h" +#include "liblvgl/lvgl.h" +#include "robodash/detail/styles.h" + +// ============================== Constructor ============================== // + +rd::Settings::Settings(std::string name) : view(name) { + lv_obj_set_style_bg_color(view, color_bg, 0); + + lv_obj_t *title_label = lv_label_create(view); + lv_obj_add_style(title_label, &style_text_large, 0); + lv_obj_add_style(title_label, &style_text_centered, 0); + lv_label_set_text(title_label, name.c_str()); + lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 12); + + // TODO: Display only when change occurs + lv_obj_t *save_btn = lv_btn_create(view); + lv_obj_set_size(save_btn, 64, 32); + lv_obj_align(save_btn, LV_ALIGN_TOP_LEFT, 4, 4); + lv_obj_add_style(save_btn, &style_core_button, 0); + lv_obj_add_style(save_btn, &style_core_button_pr, LV_STATE_PRESSED); + + lv_obj_t *save_label = lv_label_create(save_btn); + lv_label_set_text(save_label, "Save " LV_SYMBOL_SAVE); + lv_obj_align(save_label, LV_ALIGN_CENTER, 0, 0); + + settings_cont = lv_obj_create(view); + lv_obj_set_size(settings_cont, lv_pct(100), 192); + lv_obj_align(settings_cont, LV_ALIGN_CENTER, 0, 0); + lv_obj_add_style(settings_cont, &style_transp, 0); + lv_obj_set_layout(settings_cont, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(settings_cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align( + settings_cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER + ); +} + +// =========================== Private Functions =========================== // + +lv_obj_t *rd::Settings::create_setting_cont(std::string key) { + lv_obj_t *setting_cont = lv_obj_create(settings_cont); + lv_obj_set_size(setting_cont, lv_pct(80), 32); + lv_obj_add_style(setting_cont, &style_transp, 0); + + lv_obj_t *setting_label = lv_label_create(setting_cont); + lv_obj_add_style(setting_label, &style_text_large, 0); + lv_obj_align(setting_label, LV_ALIGN_LEFT_MID, 8, 0); + lv_label_set_text(setting_label, key.c_str()); + + return setting_cont; +} + +void rd::Settings::toggle_cb(lv_event_t *event) { + lv_obj_t *toggle = lv_event_get_target(event); + bool state = lv_obj_has_state(toggle, LV_STATE_CHECKED); +} + +// ============================ Public Functions ============================ // + +void rd::Settings::button(std::string key, std::function callback) { + // TODO: Implement +} + +rd::SettingValue &rd::Settings::toggle(std::string key, bool default_value) { + lv_obj_t *setting_cont = create_setting_cont(key); + lv_obj_t *setting_toggle = lv_switch_create(setting_cont); + lv_obj_set_size(setting_toggle, 48, 24); + lv_obj_align(setting_toggle, LV_ALIGN_RIGHT_MID, -8, 0); +} + +rd::SettingValue &rd::Settings::dropdown( + std::string key, std::vector values, std::string default_value +) { + lv_obj_t *setting_cont = create_setting_cont(key); + lv_obj_t *setting_dropdown = lv_dropdown_create(setting_cont); + lv_obj_set_size(setting_dropdown, 96, 24); + lv_obj_align(setting_dropdown, LV_ALIGN_RIGHT_MID, -8, 0); +} + +rd::SettingValue & +rd::Settings::slider(std::string key, double min, double max, double step, double default_value) { + lv_obj_t *setting_cont = create_setting_cont(key); + lv_obj_t *setting_slider = lv_slider_create(setting_cont); + lv_obj_set_size(setting_slider, 96, 8); + lv_obj_align(setting_slider, LV_ALIGN_RIGHT_MID, -8, 0); +} + +rd::SettingValue & +rd::Settings::increment(std::string key, int min, int max, int default_value) { + lv_obj_t *setting_cont = create_setting_cont(key); + + lv_obj_t *increment_cont = lv_obj_create(setting_cont); + lv_obj_set_size(increment_cont, 96, 24); + lv_obj_align(increment_cont, LV_ALIGN_RIGHT_MID, -8, 0); + lv_obj_add_style(increment_cont, &style_transp, 0); + + lv_obj_t *down_btn = lv_btn_create(increment_cont); + lv_obj_set_size(down_btn, 24, 24); + lv_obj_align(down_btn, LV_ALIGN_LEFT_MID, 8, 0); + lv_obj_add_style(down_btn, &style_btn_outline, 0); + lv_obj_t *down_label = lv_label_create(down_btn); + lv_label_set_text(down_label, LV_SYMBOL_MINUS); + lv_obj_align(down_label, LV_ALIGN_CENTER, 0, 0); + + lv_obj_t *number = lv_label_create(increment_cont); + lv_label_set_text_fmt(number, "%d", default_value); + lv_obj_align(number, LV_ALIGN_CENTER, 0, 0); + + lv_obj_t *up_btn = lv_btn_create(increment_cont); + lv_obj_set_size(up_btn, 24, 24); + lv_obj_align(up_btn, LV_ALIGN_RIGHT_MID, -8, 0); + lv_obj_add_style(up_btn, &style_btn_outline, 0); + lv_obj_t *up_label = lv_label_create(up_btn); + lv_label_set_text(up_label, LV_SYMBOL_PLUS); + lv_obj_align(up_label, LV_ALIGN_CENTER, 0, 0); +} + +void rd::Settings::focus() { view.focus(); }