diff --git a/README.md b/README.md index 2642378..f7ea743 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![GEM](http://spirik.ru/downloads/misc/gem/gem-logo.svg) =========== -GEM (a.k.a. *Good Enough Menu*) - Arduino library for creation of graphic multi-level menu with editable menu items, such as variables (supports `int`, `byte`, `float`, `double`, `boolean`, `char[17]` data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. +GEM (a.k.a. *Good Enough Menu*) - Arduino library for creation of graphic multi-level menu with editable menu items, such as variables (supports `int`, `byte`, `float`, `double`, `bool`, `char[17]` data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific context, which can have its own enter (setup) and exit callbacks as well as loop function. @@ -12,7 +12,7 @@ Supports buttons that can invoke user-defined actions and create action-specific Supports [AltSerialGraphicLCD](http://www.jasspa.com/serialGLCD.html) (since GEM ver. 1.0), [U8g2](https://github.com/olikraus/U8g2_Arduino) (since GEM ver. 1.1) and [Adafruit GFX](https://learn.adafruit.com/adafruit-gfx-graphics-library) (since GEM ver. 1.3) graphics libraries. -> Note that each of AltSerialGraphicLCD, U8g2 and Adafruit GFX libraries are required by default, regardless of which one of them is actually used to drive display (although the ones that are not used shouldn't affect compiled sketch size much). However, it is possible (since GEM ver. 1.2.2) to exclude support for not used ones. See [Configuration](#configuration) section for details. +> Note that U8g2 and Adafruit GFX libraries are required by default, regardless of which one of them is actually used to drive display (although the ones that are not used shouldn't affect compiled sketch size much). However, it is possible (since GEM ver. 1.2.2) to exclude support for not used ones. Support for `AltSerialGraphicLCD` is disabled by default since GEM ver. 1.5 (it is still possible to enable it). See [Configuration](#configuration) section for details. > For use with AltSerialGraphicLCD library (by Jon Green) LCD screen must be equipped with [SparkFun Graphic LCD Serial Backpack](https://www.sparkfun.com/products/9352) and properly set up to operate using firmware provided with aforementioned library. @@ -34,9 +34,12 @@ Supports [AltSerialGraphicLCD](http://www.jasspa.com/serialGLCD.html) (since GEM * [GEMPage](#gempage) * [GEMItem](#gemitem) * [GEMSelect](#gemselect) + * [GEMSpinner](#gemspinner) * [GEMCallbackData](#gemcallbackdata) - * [AppContext](#appcontext) + * [GEMAppearance](#gemappearance) + * [GEMContext](#gemcontext) * [Floating-point variables](#floating-point-variables) +* [Advanced Mode](#advanced-mode) * [Configuration](#configuration) * [Compatibility](#compatibility) * [Examples](#examples) @@ -60,14 +63,15 @@ Menu created with GEM library comprises of three base elements: Installation ------------ -Library format is compatible with Arduino IDE 1.5.x+. There are two ways to install the library: +Library format is compatible with Arduino IDE 1.5.x+. There are number of ways to install the library: - Download ZIP-archive directly from [Releases](https://github.com/Spirik/GEM/releases) section (or Master branch) and extract it into GEM folder inside your Library folder. - Using Library Manager (since Arduino IDE 1.6.2): navigate to `Sketch > Include Library > Manage Libraries` inside your Arduino IDE and search for GEM library, then click `Install`. (Alternatively you can add previously downloaded ZIP through `Sketch > Include Library > Add .ZIP Library` menu). +- Using Library Manager in Arduino IDE 2: see [documentation](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library) for details. Whichever option you choose you may need to reload IDE afterwards. -Each of [AltSerialGraphicLCD](http://www.jasspa.com/serialGLCD.html), [U8g2](https://github.com/olikraus/U8g2_Arduino) and [Adafruit GFX](https://learn.adafruit.com/adafruit-gfx-graphics-library) libraries are required to be installed by default as well. However, it is possible (since GEM ver. 1.2.2) to exclude support for not used ones. See [Configuration](#configuration) section for details. +[U8g2](https://github.com/olikraus/U8g2_Arduino) and [Adafruit GFX](https://learn.adafruit.com/adafruit-gfx-graphics-library) libraries are required to be installed by default as well. Support for [AltSerialGraphicLCD](http://www.jasspa.com/serialGLCD.html) is disabled by default since GEM ver. 1.5 (so it is not required to be installed). However, it is possible to exclude support for not used libraries (since GEM ver. 1.2.2), and/or enable support for `AltSerialGraphicLCD` (since GEM ver 1.5). See [Configuration](#configuration) section for details. How to use with AltSerialGraphicLCD ----------------------------------- @@ -77,7 +81,7 @@ How to use with AltSerialGraphicLCD ### Requirements -GEM supports [AltSerialGraphicLCD](http://www.jasspa.com/serialGLCD.html) library. LCD screen must be equipped with [SparkFun Graphic LCD Serial Backpack](https://www.sparkfun.com/products/9352) and properly set up to operate using firmware provided with AltSerialGraphicLCD. Installation and configuration of it is covered in great detail in AltSerialGraphicLCD manual. +GEM supports [AltSerialGraphicLCD](http://www.jasspa.com/serialGLCD.html) library if enabled (see [Configuration](#configuration) section). LCD screen must be equipped with [SparkFun Graphic LCD Serial Backpack](https://www.sparkfun.com/products/9352) and properly set up to operate using firmware provided with AltSerialGraphicLCD. Installation and configuration of it is covered in great detail in AltSerialGraphicLCD manual. In theory GEM is compatible with any display, that is supported by SparkFun Graphic LCD Serial Backpack. Guaranteed to work with [128x64](https://www.sparkfun.com/products/710) pixel displays. [160x128](https://www.sparkfun.com/products/8799) pixel ones should work fine as well, although it wasn't tested. @@ -112,7 +116,7 @@ Assume you have a simple setup as follows: ![Basic example breadboard](https://github.com/Spirik/GEM/wiki/images/ex_GEM_01_basic_bb_edited_1776_o.png) -Let's create a simple one page menu with one editable menu item associated with `int` variable, one with `boolean` variable, and a button, pressing of which will result in `int` variable value being printed to Serial monitor if `boolean` variable is set to `true`. To navigate through menu we will use 6 push-buttons connected to the Arduino (for four directional controls, one Cancel, and one Ok). For the sake of simplicity we will use KeyDetector library to detect single button presses (as we need a way to prevent continuously pressed button from triggering press event multiple times in a row). +Let's create a simple one page menu with one editable menu item associated with `int` variable, one with `bool` variable, and a button, pressing of which will result in `int` variable value being printed to Serial monitor if `bool` variable is set to `true`. To navigate through menu we will use 6 push-buttons connected to the Arduino (for four directional controls, one Cancel, and one Ok). For the sake of simplicity we will use KeyDetector library to detect single button presses (as we need a way to prevent continuously pressed button from triggering press event multiple times in a row). > For more detailed examples and tutorials please visit GEM [wiki](https://github.com/Spirik/GEM/wiki). @@ -160,7 +164,7 @@ Initialize an instance of the SoftwareSerial library called `serialLCD`: SoftwareSerial serialLCD(rxPin, txPin); ``` -Create an instance of the `GLCD` class named `glcd`. This instance is used to call all the subsequent GLCD functions (internally from GEM library, or manually in your sketch if it is required). Instance is created with a reference to the software serial object: +Create an instance of the `GLCD` class named `glcd`. This instance is used to call all of the subsequent GLCD functions (internally from GEM library, or manually in your sketch if it is required). Instance is created with a reference to the software serial object: ```cpp GLCD glcd(serialLCD); @@ -174,7 +178,7 @@ Create variables that you would like to be editable through the menu. Assign the ```cpp int number = -512; -boolean enablePrint = false; +bool enablePrint = false; ``` Create two menu item objects of class `GEMItem`, linked to `number` and `enablePrint` variables. Let's name them simply "Number" and "Enable print" respectively - these names will be printed on screen: @@ -358,7 +362,7 @@ void printData() { } ``` -> This is the simplest action that menu item button can have. More elaborate versions make use of custom "[context](#appcontext)" that can be created when button is pressed. In that case, button action can have its own setup and loop functions (named `context.enter()` and `context.loop()`) that run similarly to how sketch operates. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. +> This is the simplest action that menu item button can have. More elaborate versions make use of custom "[context](#gemcontext)" that can be created when button is pressed. In that case, button action can have its own setup and loop functions (named `context.enter()` and `context.loop()`) that run similarly to how sketch operates. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. #### Sketch @@ -405,7 +409,7 @@ Assume you have a simple setup as follows: ![Basic example breadboard](https://github.com/Spirik/GEM/wiki/images/ex_GEM_01_basic_u8g2_breadboard_bb_edited_1974_o.png) -Let's create a simple one page menu with one editable menu item associated with `int` variable, one with `boolean` variable, and a button, pressing of which will result in `int` variable value being printed to Serial monitor if `boolean` variable is set to `true`. To navigate through menu we will use 6 push-buttons connected to the Arduino (for four directional controls, one Cancel, and one Ok). We will use U8g2 library to detect single button presses. +Let's create a simple one page menu with one editable menu item associated with `int` variable, one with `bool` variable, and a button, pressing of which will result in `int` variable value being printed to Serial monitor if `bool` variable is set to `true`. To navigate through menu we will use 6 push-buttons connected to the Arduino (for four directional controls, one Cancel, and one Ok). We will use U8g2 library to detect single button presses. > For more detailed examples and tutorials please visit GEM [wiki](https://github.com/Spirik/GEM/wiki). @@ -413,7 +417,7 @@ Let's create a simple one page menu with one editable menu item associated with U8g2 library supports numerous popular display controllers. Choose a matching constructor for the correct initialization of the display. See available constructors and supported controllers in the [documentation](https://github.com/olikraus/u8g2/wiki/u8g2setupcpp) for U8g2 library. -In our case create an instance of the `U8G2_KS0108_128X64_1` class named `u8g2`. This instance is used to call all the subsequent U8g2 functions (internally from GEM library, or manually in your sketch if it is required). +In our case create an instance of the `U8G2_KS0108_128X64_1` class named `u8g2`. This instance is used to call all of the subsequent U8g2 functions (internally from GEM library, or manually in your sketch if it is required). ```cpp U8G2_KS0108_128X64_1 u8g2(U8G2_R0, 8, 9, 10, 11, 12, 13, 18, 19, /*enable=*/ A0, /*dc=*/ A1, /*cs0=*/ A3, /*cs1=*/ A2, /*cs2=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE); @@ -429,7 +433,7 @@ Create variables that you would like to be editable through the menu. Assign the ```cpp int number = -512; -boolean enablePrint = false; +bool enablePrint = false; ``` Create two menu item objects of class `GEMItem`, linked to `number` and `enablePrint` variables. Let's name them simply "Number" and "Enable print" respectively - these names will be printed on screen: @@ -574,7 +578,7 @@ void printData() { } ``` -> This is the simplest action that menu item button can have. More elaborate versions make use of custom "[context](#appcontext)" that can be created when button is pressed. In that case, button action can have its own setup and loop functions (named `context.enter()` and `context.loop()`) that run similarly to how sketch operates. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. +> This is the simplest action that menu item button can have. More elaborate versions make use of custom "[context](#gemcontext)" that can be created when button is pressed. In that case, button action can have its own setup and loop functions (named `context.enter()` and `context.loop()`) that run similarly to how sketch operates. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. #### Sketch @@ -632,7 +636,7 @@ Assume you have a simple setup as follows: ![Basic example breadboard](https://raw.githubusercontent.com/wiki/Spirik/GEM/images/ex_GEM_01_basic_agfx_breadboard_bb_edited_1590_o.png) -Let's create a simple one page menu with one editable menu item associated with `int` variable, one with `boolean` variable, and a button, pressing of which will result in `int` variable value being printed to Serial monitor if `boolean` variable is set to `true`. To navigate through menu we will use 6 push-buttons connected to the Arduino (for four directional controls, one Cancel, and one Ok). For the sake of simplicity we will use KeyDetector library to detect single button presses (as we need a way to prevent continuously pressed button from triggering press event multiple times in a row). +Let's create a simple one page menu with one editable menu item associated with `int` variable, one with `bool` variable, and a button, pressing of which will result in `int` variable value being printed to Serial monitor if `bool` variable is set to `true`. To navigate through menu we will use 6 push-buttons connected to the Arduino (for four directional controls, one Cancel, and one Ok). For the sake of simplicity we will use KeyDetector library to detect single button presses (as we need a way to prevent continuously pressed button from triggering press event multiple times in a row). > For more detailed examples and tutorials please visit GEM [wiki](https://github.com/Spirik/GEM/wiki). @@ -669,7 +673,7 @@ Navigation buttons initial setup is now complete. Adafruit GFX library supports several different display controllers (through separately installed and included libraries). Choose a matching library for the correct initialization of the display. See available libraries and supported controllers in the [documentation](https://learn.adafruit.com/adafruit-gfx-graphics-library) for Adafruit GFX library. -In our case we included Adafruit_ST7735 library, and now we need to create an instance of the `Adafruit_ST7735` class named `tft`. This instance is used to call all the subsequent Adafruit GFX functions (internally from GEM library, or manually in your sketch if it is required). Before that, we define aliases for the pins display is connected to. +In our case we included Adafruit_ST7735 library, and now we need to create an instance of the `Adafruit_ST7735` class named `tft`. This instance is used to call all of the subsequent Adafruit GFX functions (internally from GEM library, or manually in your sketch if it is required). Before that, we define aliases for the pins display is connected to. ```cpp #define TFT_CS A2 @@ -687,7 +691,7 @@ Create variables that you would like to be editable through the menu. Assign the ```cpp int number = -512; -boolean enablePrint = false; +bool enablePrint = false; ``` Create two menu item objects of class `GEMItem`, linked to `number` and `enablePrint` variables. Let's name them simply "Number" and "Enable print" respectively - these names will be printed on screen: @@ -751,7 +755,7 @@ You can optionally [rotate](https://learn.adafruit.com/adafruit-gfx-graphics-lib tft.setRotation(3); ``` -Init menu. That will run some initialization routines (e.g. load sprites into LCD Serial Backpack's internal memory), then show splash screen (which can be customized). +Init menu. That will run some initialization routines, then show splash screen (which can be customized). ```cpp menu.init(); @@ -864,7 +868,7 @@ void printData() { } ``` -> This is the simplest action that menu item button can have. More elaborate versions make use of custom "[context](#appcontext)" that can be created when button is pressed. In that case, button action can have its own setup and loop functions (named `context.enter()` and `context.loop()`) that run similarly to how sketch operates. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. +> This is the simplest action that menu item button can have. More elaborate versions make use of custom "[context](#gemcontext)" that can be created when button is pressed. In that case, button action can have its own setup and loop functions (named `context.enter()` and `context.loop()`) that run similarly to how sketch operates. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. #### Sketch @@ -885,24 +889,30 @@ Reference ### GEM, GEM_u8g2, GEM_adafruit_gfx -Primary class of library. Responsible for appearance of the menu, communication with LCD screen (via supplied `GLCD`, `U8G2` or `Adafruit_GFX` object), integration of all menu items `GEMItem` and pages `GEMPage` into one menu. Object of corresponding `GEM` class variation defines as follows. +Primary class of the library. Responsible for general appearance of the menu, communication with display (via supplied `GLCD`, `U8G2` or `Adafruit_GFX` object), integration of all menu items `GEMItem` and pages `GEMPage` into one menu. Object of corresponding `GEM` class variation defines as follows. AltSerialGraphicLCD version: ```cpp GEM menu(glcd[, menuPointerType[, menuItemsPerScreen[, menuItemHeight[, menuPageScreenTopOffset[, menuValuesLeftOffset]]]]]); +// or +GEM menu(glcd[, appearance]); ``` U8g2 version: ```cpp GEM_u8g2 menu(u8g2[, menuPointerType[, menuItemsPerScreen[, menuItemHeight[, menuPageScreenTopOffset[, menuValuesLeftOffset]]]]]); +// or +GEM_u8g2 menu(u8g2[, appearance]); ``` Adafruit GFX version: ```cpp GEM_adafruit_gfx menu(tft[, menuPointerType[, menuItemsPerScreen[, menuItemHeight[, menuPageScreenTopOffset[, menuValuesLeftOffset]]]]]); +// or +GEM_adafruit_gfx menu(tft[, appearance]); ``` * **glcd** `AltSerialGraphicLCD version` @@ -949,13 +959,33 @@ GEM_adafruit_gfx menu(tft[, menuPointerType[, menuItemsPerScreen[, menuItemHeigh *Default*: `86` Offset from the left of the screen to the value of variable associated with the menu item (effectively the space left for the title of the menu item to be printed on screen). Default value is suitable for 128x64 screen with other parameters at their default values; 86 - recommended value for 128x64 screen. +* **appearance** [*optional*] + *Type*: `GEMAppearance` + Object of type `GEMAppearance` that holds values of appearance settings that define how menu is rendered on screen. Essentially allows to pass appearance as a single object instead of specifying each option as a separate argument. + ![GEM customization](https://github.com/Spirik/GEM/wiki/images/customization.gif) -> **Note:** carefully choose values of `menuItemsPerScreen`, `menuItemHeight`, `menuPageScreenTopOffset`, `menuValuesLeftOffset` in accordance to the actual size of your LCD screen. Default values of these options are suitable for 128x64 screens. But that is not the only possible option: the other combination of values you set may also be suitable - just calculate them correctly and see what works best for you. +Calls to `GEM`, `GEM_u8g2` or `GEM_adafruit_gfx` constructors `GEM(glcd)`, `GEM_u8g2(u8g2)`, `GEM_adafruit_gfx(tft)` without specifying additional custom parameters are equivalent to the following calls: + +```cpp +GEM menu(glcd, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); +``` + +```cpp +GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); +``` + +```cpp +GEM_adafruit_gfx menu(tft, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); +``` + +> **Note:** carefully choose values of `menuItemsPerScreen`, `menuItemHeight`, `menuPageScreenTopOffset`, `menuValuesLeftOffset` in accordance to the actual size of your display. Default values of these options are suitable for 128x64 screens. But that is not the only possible option: the other combination of values you set may also be suitable - just calculate them correctly and see what works best for you. > **Note:** long title of the menu page `GEMPage` won't overflow to the new line in U8g2 version and will be truncated at the edge of the screen. -For more details on customization see corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). +> **Note:** it is possible to customize appearance of each menu page individually (since GEM ver. 1.5) by using [`GEMAppearance`](#gemappearance) object. + +For more details on customization see corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki) and description of [`GEMAppearance`](#gemappearance) struct. #### Constants @@ -974,6 +1004,36 @@ For more details on customization see corresponding section of the [wiki](https: *Value*: `0` Alias for the option to automatically determine the number of menu items that will fit on the screen based on actual height of the screen (submitted as **menuItemsPerScreen** setting to `GEM`, `GEM_u8g2` and `GEM_adafruit_gfx` constructors). +* **GEM_FONT_BIG** + * `GEM_adafruit_gfx`: + *Type*: macro `#define GEM_FONT_BIG &Fixed6x12` + *Value*: `&Fixed6x12` + * `GEM_u8g2`: + *Type*: macro `#define GEM_FONT_BIG u8g2_font_6x12_tr` + *Value*: `u8g2_font_6x12_tr` + + Alias for the default big font version used to print menu items. Submitted as a default value to `GEM_adafruit_gfx::setFontBig()` method. + +* **GEM_FONT_BIG_CYR** `GEM_u8g2 version only` + *Type*: macro `#define GEM_FONT_BIG_CYR u8g2_font_6x12_t_cyrillic` + *Value*: `u8g2_font_6x12_t_cyrillic` + Alias for the default Cyrillic big font version used to print menu items. + +* **GEM_FONT_SMALL** + * `GEM_adafruit_gfx`: + *Type*: macro `#define GEM_FONT_SMALL &TomThumbMono` + *Value*: `&TomThumbMono` + * `GEM_u8g2`: + *Type*: macro `#define GEM_FONT_SMALL u8g2_font_tom_thumb_4x6_tr` + *Value*: `u8g2_font_tom_thumb_4x6_tr` + + Alias for the default small font version used to print titles of menu pages (and menu items when big font won't fit). Submitted as a default value to `GEM_adafruit_gfx::setFontSmall()` method. + +* **GEM_FONT_SMALL_CYR** `GEM_u8g2 version only` + *Type*: macro `#define GEM_FONT_SMALL_CYR u8g2_font_4x6_t_cyrillic` + *Value*: `u8g2_font_4x6_t_cyrillic` + Alias for the default Cyrillic small font version used to print titles of menu pages (and menu items when big font won't fit). + * **GEM_KEY_NONE** *Type*: macro `#define GEM_KEY_NONE 0` *Value*: `0` @@ -1037,49 +1097,68 @@ For more details on customization see corresponding section of the [wiki](https: *Type*: macro `#define GEM_KEY_OK U8X8_MSG_GPIO_MENU_SELECT` *Value*: `U8X8_MSG_GPIO_MENU_SELECT` - Alias for the keys (buttons) used to navigate and interact with menu. Submitted to `GEM::registerKeyPress()`, `GEM_u8g2::registerKeyPress()` and `GEM_adafruit_gfx::registerKeyPress()` methods. Indicates that Ok/Apply key is pressed (toggle boolean menu item, enter edit mode of the associated non-boolean variable, exit edit mode with saving the variable, execute code associated with button). + Alias for the keys (buttons) used to navigate and interact with menu. Submitted to `GEM::registerKeyPress()`, `GEM_u8g2::registerKeyPress()` and `GEM_adafruit_gfx::registerKeyPress()` methods. Indicates that Ok/Apply key is pressed (toggle `bool` menu item, enter edit mode of the associated non-`bool` variable, exit edit mode with saving the variable, execute code associated with button). #### Methods -* **setSplash(** _const uint8_t PROGMEM_ *sprite **)** `AltSerialGraphicLCD version` +* *GEM&* **setAppearance(** _GEMAppearance_ appearance **)** + *Accepts*: `GEMAppearance` + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` + Set general [appearance](#gemappearance) of the menu (can be overridden in `GEMPage` on per page basis). + +* *GEMAppearance** **getCurrentAppearance()** + *Returns*: `GEMAppearance*` + Get appearance (as a pointer to [`GEMAppearance`](#gemappearance) object) applied to current menu page (or general if menu page has none of its own). + +* *GEM&* **setSplash(** _const uint8_t PROGMEM_ *sprite **)** `AltSerialGraphicLCD version` *Accepts*: `_const uint8_t PROGMEM_ *` - *Returns*: nothing + *Returns*: `GEM&` Set custom sprite displayed as the splash screen when GEM is being initialized. Should be called before `GEM::init()`. The following is the format of the sprite as described in AltSerialGraphicLCD library documentation: > The sprite commences with two bytes which are the width and height of the image in pixels. The pixel data is organised as rows of 8 vertical pixels per byte where the least significant bit (LSB) is the top-left pixel and the most significant bit (MSB) tends towards the bottom-left pixel. A complete row of 8 vertical pixels across the image width comprises the first row, this is then followed by the next row of 8 vertical pixels and so on. Where the image height is not an exact multiple of 8 bits then any unused bits are typically set to zero (although this does not matter). For more details on splash customization see corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). -* **setSplash(** _byte_ width, _byte_ height, _const unsigned char U8X8_PROGMEM_ *image **)** `U8g2 version` +* *GEM_u8g2&* **setSplash(** _byte_ width, _byte_ height, _const unsigned char U8X8_PROGMEM_ *image **)** `U8g2 version` *Accepts*: `byte`, `byte`, `_const unsigned char U8X8_PROGMEM_ *` - *Returns*: nothing + *Returns*: `GEM_u8g2&` Set custom [XBM](https://en.wikipedia.org/wiki/X_BitMap) image displayed as the splash screen when GEM is being initialized. Should be called before `GEM_u8g2::init()`. For more details on splash customization and example refer to corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). -* **setSplash(** _byte_ width, _byte_ height, _const uint8_t PROGMEM_ *image **)** `Adafruit GFX version` +* *GEM_adafruit_gfx&* **setSplash(** _byte_ width, _byte_ height, _const uint8_t PROGMEM_ *image **)** `Adafruit GFX version` *Accepts*: `byte`, `byte`, `_const uint8_t PROGMEM_ *` - *Returns*: nothing + *Returns*: `GEM_adafruit_gfx&` Set custom bitmap image displayed as the splash screen when GEM is being initialized. The following is the format of the bitmap as described in Adafruit GFX library documentation: > A contiguous block of bits, where each `1` bit sets the corresponding pixel to 'color', while each `0` bit is skipped. Bitmap must be presented as a byte array and located in program memory using the PROGMEM directive, width and height of the bitmap must be supplied as a first and second argument to the function respectively. Should be called before `GEM_adafruit_gfx::init()`. See [Bitmaps](https://learn.adafruit.com/adafruit-gfx-graphics-library/graphics-primitives#bitmaps-2002806-39) section of Adafruit GFX documentation for more details and [image2cpp](http://javl.github.io/image2cpp/) webtool for online bitmap conversion. For more details on splash customization and example refer to corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). -* **setSplashDelay(** _uint16_t_ value **)** +* *GEM&* **setSplashDelay(** _uint16_t_ value **)** *Accepts*: `uint16_t` - *Returns*: nothing + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Set splash screen delay (in ms). By default splash screen will be visible for 1000ms. Maximum supported value is 65535ms. Setting to 0 will disable splash screen. Should be called before `init()`. > **Note:** internally splash screen delay is implemented via `delay()` function. This is the only place in library where `delay()` is utilized (aside of example sketches). -* **hideVersion(** _boolean_ flag = true **)** - *Accepts*: `boolean` - *Returns*: nothing +* *GEM&* **hideVersion(** _bool_ flag = true **)** + *Accepts*: `bool` + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Turn printing of the current GEM library version on splash screen off (`hideVersion()`) or back on (`hideVersion(false)`). By default the version is printed. Should be called before `init()`. -* **enableCyrillic(** _boolean_ flag = true **)** `U8g2 version only` - *Accepts*: `boolean` - *Returns*: nothing - Turn Cyrillic typeset on (`enableCyrillic()`) or off (`enableCyrillic(false)`). [`u8g2_font_6x12_t_cyrillic`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_6x12_t_cyrillic.png) and [`u8g2_font_4x6_t_cyrillic`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_4x6_t_cyrillic.png) fonts from [U8g2](https://github.com/olikraus/u8g2/wiki/fntlistall) will be used when Cyrillic typeset is enabled, and default fonts [`u8g2_font_6x12_tr`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_6x12_tr.png) and [`u8g2_font_tom_thumb_4x6_tr`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_tom_thumb_4x6_tr.png) will be used otherwise. You may use Cyrillic in menu title, menu item labels (`GEMItem`, including buttons and menu page links), and select options (`SelectOptionInt`, `SelectOptionByte`, `SelectOptionChar` data structures). Editable strings with Cyrillic characters are **not supported** (edit mode of such strings may lead to unpredictable results due to incompatibility with 2-byte characters). Increases required program storage space, use cautiously. By default Cyrillic typeset is off. Should be called before `GEM_u8g2::init()`. +* *GEM_u8g2&* **enableUTF8(** _bool_ flag = true **)** `U8g2 version only` + *Accepts*: `bool` + *Returns*: `GEM_u8g2&` + Turn support for multi-byte UTF8 fonts on (`enableUTF8()`) or off (`enableUTF8(false)`). Fonts are set using `GEM_u8g2::setFontBig()` and `GEM_u8g2::setFontSmall()` methods. You may use UTF8 fonts in menu title, menu item labels (`GEMItem`, including buttons and menu page links), and select options (`SelectOptionInt`, `SelectOptionByte`, `SelectOptionChar` data structures). Editable strings with UTF8 characters are **not supported** (edit mode of such strings may lead to unpredictable results due to incompatibility with multi-byte UTF8 characters). By default support for UTF8 is off. Should be called before `GEM_u8g2::init()`. + +* *GEM_u8g2&* **enableCyrillic(** _bool_ flag = true **)** `U8g2 version only` + *Accepts*: `bool` + *Returns*: `GEM_u8g2&` + Turn Cyrillic typeset on (`enableCyrillic()`) or off (`enableCyrillic(false)`). By default [`u8g2_font_6x12_t_cyrillic`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_6x12_t_cyrillic.png) and [`u8g2_font_4x6_t_cyrillic`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_4x6_t_cyrillic.png) fonts from [U8g2](https://github.com/olikraus/u8g2/wiki/fntlistall) will be used when Cyrillic typeset is enabled, and fonts [`u8g2_font_6x12_tr`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_6x12_tr.png) and [`u8g2_font_tom_thumb_4x6_tr`](https://raw.githubusercontent.com/wiki/olikraus/u8g2/fntpic/u8g2_font_tom_thumb_4x6_tr.png) will be used otherwise (if not overridden via `GEM_u8g2::setFontBig()` and `GEM_u8g2::setFontSmall()` methods). You may use Cyrillic in menu title, menu item labels (`GEMItem`, including buttons and menu page links), and select options (`SelectOptionInt`, `SelectOptionByte`, `SelectOptionChar` data structures). Editable strings with Cyrillic characters are **not supported** (edit mode of such strings may lead to unpredictable results due to incompatibility with multi-byte UTF8 characters). Increases required program storage space, use cautiously. By default Cyrillic typeset is off. Should be called before `GEM_u8g2::init()`. -* **init()** - *Returns*: nothing +* *GEM&* **invertKeysDuringEdit(** _bool_ invert = true **)** + *Accepts*: `bool` + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` + Turn inverted order of characters during edit mode on (`invertKeysDuringEdit()`) or off (`invertKeysDuringEdit(false)`). By default when in edit mode of a number or a `char[17]` variable, digits (and other characters) increment when `GEM_KEY_UP` key is pressed and decrement when `GEM_KEY_DOWN` key is pressed. Inverting this order may lead to more natural expected behavior when editing `char[17]` or number variables with certain input devices (e.g. rotary encoder, in which case rotating knob clock-wise is generally associated with `GEM_KEY_DOWN` action during navigation through menu items, but in edit mode it seems more natural to increment a digit rather than to decrement it when performing the same clock-wise rotation). + +* *GEM&* **init()** + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Init the menu: load necessary sprites into RAM of the SparkFun Graphic LCD Serial Backpack (for AltSerialGraphicLCD version), display GEM splash screen, etc. > The following `GLCD` object settings will be applied during `init()`: > * `glcd.drawMode(GLCD_MODE_NORMAL)`; @@ -1096,53 +1175,102 @@ For more details on customization see corresponding section of the [wiki](https: > Keep this in mind if you are planning to use the same object in your own routines. > The following `Adafruit_GFX` object settings will be applied during `init()`: - > * `tft.setTextSize(1)`; + > * `tft.setTextSize(_textSize)` (sets text magnification size to default value 1 or value set through `setTextSize()` previously); > * `tft.setTextWrap(false)`; > * `tft.setTextColor(_menuForegroundColor)` (sets text color to default value 0xFFFF or value set through `setForegroundColor()`). > > Keep this in mind if you are planning to use the same object in your own routines. -* **reInit()** - *Returns*: nothing - Set GEM specific settings to their values, set initially in `init()` method. If you were working with AltSerialGraphicLCD, U8g2 or Adafruit GFX graphics in your own user-defined button action, it may be a good idea to call `reInit()` before drawing menu back to screen (generally in custom `context.exit()` routine). See [context](#appcontext) for more details. - -* **setForegroundColor(** _uint16_t_ color **)** `Adafruit GFX version only` +* *GEM&* **reInit()** + **Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` + Set GEM specific settings to their values, set initially in `init()` method. If you were working with AltSerialGraphicLCD, U8g2 or Adafruit GFX graphics in your own user-defined button action, it may be a good idea to call `reInit()` before drawing menu back to screen (generally in custom `context.exit()` routine). See [context](#gemcontext) for more details. + +* *GEM_adafruit_gfx&* **setTextSize(** _uint8_t_ size **)** `Adafruit GFX version only` + *Accepts*: `uint8_t` + *Returns*: `GEM_adafruit_gfx&` + Set text 'magnification' size (as per Adafruit GFX docs) to supplied value. See Adafruit GFX [documentation](http://adafruit.github.io/Adafruit-GFX-Library/html/class_adafruit___g_f_x.html#a39eb4a8a2c9fa4ab7d58ceffd19535d5) for details on internaly called method of the same name. Sprites (i.e. various menu icons) will be scaled maximum up to two times regardless of the value. If not called explicitly, magnification size will be set to 1. Should be called before `init()`. + +* *GEM_adafruit_gfx&* **setSpriteSize(** _uint8_t_ size **)** `Adafruit GFX version only` + *Accepts*: `uint8_t` + *Returns*: `GEM_adafruit_gfx&` + Set sprite scaling factor if it should be different from the text 'magnification' size set through `setTextSize()` method. Sprites (i.e. various menu icons) will be scaled maximum up to two times regardless of the value. If not called explicitly, equals to the text magnification set with `setTextSize()`. Should be called after `setTextSize()` but before `init()`. + +* *GEM_adafruit_gfx&* **setFontBig(** _const GFXfont*_ font = GEM_FONT_BIG[, _byte_ width = 6[, _byte_ height = 8[, _byte_ baselineOffset = 8]]] **)** `Adafruit GFX version` + *Accepts*: `const GFXfont*`[, `byte`[, `byte`[, `byte`]]] + *Returns*: `GEM_adafruit_gfx&` + Set font that will be displayed if height of menu item is enough to fit it (else small font will be used instead). Accepts pointer to the font that Adafruit GFX supports. See Adafruit GFX [documentation](https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts) for details on font format and possible conversion options. Note that using mono-spaced font is recommended for correct calculations internally (especially for editable menu items). Options `width` and `height` describe size of a single character, `baselineOffset` sets offset from the top of the character to baseline (helps to better adjust vertical alignment). Call this method without arguments to revert to default font supplied with GEM. + +* *GEM_adafruit_gfx&* **setFontSmall(** _const GFXfont*_ font = GEM_FONT_SMALL[, _byte_ width = 4[, _byte_ height = 6[, _byte_ baselineOffset = 6]]] **)** `Adafruit GFX version` + *Accepts*: `const GFXfont*`[, `byte`[, `byte`[, `byte`]]] + *Returns*: `GEM_adafruit_gfx&` + Set font that will be displayed if height of menu item is not enough to fit big font. Also used in titles of menu pages. Accepts pointer to the font that Adafruit GFX supports. See Adafruit GFX [documentation](https://learn.adafruit.com/adafruit-gfx-graphics-library/using-fonts) for details on font format and possible conversion options. Note that using mono-spaced font is recommended for correct calculations internally (especially for editable menu items). Options `width` and `height` describe size of a single character, `baselineOffset` sets offset from the top of the character to baseline (helps to better adjust vertical alignment). Call this method without arguments to revert to default font supplied with GEM. + +* *GEM_u8g2&* **setFontBig(** _const uint8_t*_ font[, _byte_ width = 6[, _byte_ height = 8]] **)** `U8g2 version` + *Accepts*: `const uint8_t*`[, `byte`[, `byte`]] + *Returns*: `GEM_u8g2&` + Set font that will be displayed if height of menu item is enough to fit it (else small font will be used instead). Accepts pointer to the font that U8g2 supports. See U8g2 [documentation](https://github.com/olikraus/u8g2/wiki/fntlistall) for list of available fonts. Note that using mono-spaced font is recommended for correct calculations internally (especially for editable menu items). Options `width` and `height` describe size of a single character. Adjusting `height` may help to achieve better vertical alignment. Call this method without arguments to revert to default font settings. + +* *GEM_u8g2&* **setFontSmall(** _const uint8_t*_ font[, _byte_ width = 4[, _byte_ height = 6]] **)** `U8g2 version` + *Accepts*: `const uint8_t*`[, `byte`[, `byte`]] + *Returns*: `GEM_u8g2&` + Set font that will be displayed if height of menu item is not enough to fit big font. Also used in titles of menu pages. Accepts pointer to the font that U8g2 supports. See U8g2 [documentation](https://github.com/olikraus/u8g2/wiki/fntlistall) for list of available fonts. Note that using mono-spaced font is recommended for correct calculations internally (especially for editable menu items). Options `width` and `height` describe size of a single character. Adjusting `height` may help to achieve better vertical alignment. Call this method without arguments to revert to default font settings. + +* *GEM_adafruit_gfx&* **setForegroundColor(** _uint16_t_ color **)** `Adafruit GFX version only` *Accepts*: `uint16_t` - *Returns*: nothing + *Returns*: `GEM_adafruit_gfx&` Set foreground color to supplied value. Accepts 16-bit RGB color representation. See Adafruit GFX [documentation](https://learn.adafruit.com/adafruit-gfx-graphics-library/coordinate-system-and-units) for detailed description of a format. Will take effect next time menu is drawn. If not called explicitly, foreground color will be set to 0xFFFF. For more details on color customization and example refer to corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). -* **setBackgroundColor(** _uint16_t_ color **)** `Adafruit GFX version only` +* *GEM_adafruit_gfx&* **setBackgroundColor(** _uint16_t_ color **)** `Adafruit GFX version only` *Accepts*: `uint16_t` - *Returns*: nothing + *Returns*: `GEM_adafruit_gfx&` Set background color to supplied value. Accepts 16-bit RGB color representation. See Adafruit GFX [documentation](https://learn.adafruit.com/adafruit-gfx-graphics-library/coordinate-system-and-units) for detailed description of a format. Will take effect next time menu is drawn. If not called explicitly, background color will be set to 0x0000. For more details on color customization and example refer to corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). -* **setMenuPageCurrent(** _GEMPage&_ menuPageCurrent **)** +* *GEM&* **setMenuPageCurrent(** _GEMPage&_ menuPageCurrent **)** *Accepts*: `GEMPage` - *Returns*: nothing + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Set supplied menu page as current. Accepts `GEMPage` object. -* **drawMenu()** - *Returns*: nothing +* *GEMPage** **getCurrentMenuPage()** + *Returns*: `GEMPage*` + Get pointer to currently active menu page. + +* *GEM&* **drawMenu()** + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Draw menu on screen, with menu page set earlier in `setMenuPageCurrent()`. -* *boolean* **readyForKey()** - *Returns*: `boolean` - Check that menu is waiting for the key press. +* *GEM&* **setDrawMenuCallback(** _void_ (*drawMenuCallback)() **)** + *Accepts*: `pointer to function` + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` + Specify callback function that will be called at the end of `drawMenu()`. Potentially can be used to draw something on top of the menu, e.g. notification icons in menu title, battery status, etc. However, note that for different versions of GEM drawMenuCallback may be called different number of times: e.g. U8g2 version of GEM calls `drawMenu()` method more often than other versions do, especially when buffer modes `_1` or `_2` of U8g2 is enabled. Alternatively, Adafruit GFX and AltSerialGraphicLCD versions of GEM make use of partial updates of the screen, hence call to `drawMenu()` is less common. Keep that in mind when specifying callback function, and consider using U8g2 version of GEM with full buffer `_F` mode. + +* *GEM&* **removeDrawMenuCallback()** + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` + Disable callback that was called at the end of `drawMenu()`. -* **registerKeyPress(** _byte_ keyCode **)** +* *bool* **isEditMode()** + *Returns*: `bool` + Checks if menu is in edit mode (returns `true` when editing a variable or navigating through option select or spinner). + +* *bool* **readyForKey()** + *Returns*: `bool` + Checks that menu is waiting for the key press. + +* *GEM&* **registerKeyPress(** _byte_ keyCode **)** *Accepts*: `byte` (*Values*: `GEM_KEY_NONE`, `GEM_KEY_UP`, `GEM_KEY_RIGHT`, `GEM_KEY_DOWN`, `GEM_KEY_LEFT`, `GEM_KEY_CANCEL`, `GEM_KEY_OK`) - *Returns*: nothing + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Register the key press and trigger corresponding action (navigation through the menu, editing values, pressing menu buttons). -* **clearContext()** - *Returns*: nothing +* *GEM&* **clearContext()** + *Returns*: `GEM&`, or `GEM_u8g2&`, or `GEM_adafruit_gfx&` Clear context. Assigns `nullptr` values to function pointers of the `context` property and sets `allowExit` flag of the `context` to `true`. +> **Note:** calls to methods that return a reference to the owning `GEM`, or `GEM_u8g2`, or `GEM_adafruit_gfx` object can be chained, e.g. `menu.hideVersion().invertKeysDuringEdit().init();` (since GEM ver. 1.4.6). + #### Properties * **context** - *Type*: `AppContext` - Currently set [context](#appcontext). + *Type*: `GEMContext` + Currently set [context](#gemcontext). ---------- @@ -1155,6 +1283,10 @@ Menu page holds menu items `GEMItem` and represents menu level. Menu can have mu ```cpp GEMPage menuPage(title[, exitAction]); ``` +or +```cpp +GEMPage menuPage(title[, parentMenuPage]); +``` * **title** *Type*: `const char*` @@ -1164,37 +1296,78 @@ GEMPage menuPage(title[, exitAction]); * **exitAction** [*optional*] *Type*: `pointer to function` - Pointer to a function that will be executed when `GEM_KEY_CANCEL` key is pressed while being on top level menu page (i.e. page that has no parent menu page) and not in edit mode. Action-specific [context](#appcontext) can be created, which can have its own enter (setup) and exit callbacks as well as loop function. + Pointer to a function that will be executed when `GEM_KEY_CANCEL` key is pressed while being on top level menu page (i.e. page that has no parent menu page) and not in edit mode. Action-specific [context](#gemcontext) can be created, which can have its own enter (setup) and exit callbacks as well as loop function. Current menu item will be set to the first item of the menu page upon calling this function, this change will be reflected with the subsequent explicit redraw of the menu (e.g. by calling `drawMenu()` method of `GEM`, `GEM_u8g2` or `GEM_adafruit_gfx` object). + +* **parentMenuPage** [*optional*] + *Type*: `GEMPage` + Parent level menu page (to know where to go back to when pressing Back button, which will be added automatically). Alternatively can be set by calling `GEMPage::setParentMenuPage()` method (see below). + +#### Constants + +* **GEM_LAST_POS** + *Type*: macro `#define GEM_LAST_POS 255` + *Value*: `255` + Alias for the last possible position that menu item can be added at. Submitted as a default value of **pos** option to `GEMPage::addMenuItem()` method. + +* **GEM_ITEMS_TOTAL** + *Type*: macro `#define GEM_ITEMS_TOTAL true` + *Value*: `true` + Alias for modifier of `GEMPage::addMenuItem()` method for the case when all menu items should be considered. Submitted as a default value of **total** option to `GEMPage::addMenuItem()` method. + +* **GEM_ITEMS_VISIBLE** + *Type*: macro `#define GEM_ITEMS_VISIBLE false` + *Value*: `false` + Alias for modifier of `GEMPage::addMenuItem()` method for the case when only visible menu items should be considered. Submitted as a possible value of **total** option to `GEMPage::addMenuItem()` method. #### Methods -* **addMenuItem(** _GEMItem&_ menuItem **)** - *Accepts*: `GEMItem` - *Returns*: nothing - Add menu item to menu page. Accepts `GEMItem` object. +* *GEMPage&* **addMenuItem(** _GEMItem&_ menuItem[, _byte_ pos = 255[, _bool_ total = true]] **)** + *Accepts*: `GEMItem`[, `byte`[, `bool`]] + *Returns*: `GEMPage&` + Add menu item to menu page. Accepts `GEMItem` object. Optionally menu item can be added at a specified position **pos** (zero-based number from 0 to 255, as a second argument) out of total (flag **total** set to `true`, or `GEM_ITEMS_TOTAL`, as a third argument) or only visible (flag **total** set to `false`, or `GEM_ITEMS_VISIBLE`, as a third argument) number of items. Note that if **pos** is set to 0 and menu page has parent menu page, menu item will be added at position 1 instead (i.e. as a second menu item, after built-in Back button). By default (if optional arguments are not provided) each menu item is added at the end of the list of menu items of the page (including hidden ones). -* **setParentMenuPage(** _GEMPage&_ parentMenuPage **)** +* *GEMPage&* **setParentMenuPage(** _GEMPage&_ parentMenuPage **)** *Accepts*: `GEMPage` - *Returns*: nothing - Specify parent level menu page (to know where to go back to when pressing Back button, that will be added automatically). Accepts `GEMPage` object. + *Returns*: `GEMPage&` + Specify parent level menu page (to know where to go back to when pressing Back button, which will be added automatically). Accepts `GEMPage` object. If called additional time, previously added parent menu page will be overridden with the new one. -* **setTitle(** _const char*_ title **)** - *Returns*: nothing +* *GEMPage&* **setTitle(** _const char*_ title **)** + *Returns*: `GEMPage&` Set title of the menu page. Can be used to update menu page title dynamically. * *const char** **getTitle()** *Returns*: `const char*` Get title of the menu page. +* *GEMPage&* **setAppearance(** _GEMAppearance*_ appearance **)** + *Accepts*: `GEMAppearance*` + *Returns*: `GEMPage&` + Set appearance of the menu page. Note that appearance should be passed as a pointer to [`GEMAppearance`](#gemappearance) object. And as such, `GEMAppearance` object should be declared in a global scope. + +* *GEMItem** **getMenuItem(** _byte_ index[, _bool_ total = false] **)** + *Accepts*: `byte`[, `bool`] + *Returns*: `GEMItem*` + Get pointer to menu item on this page by index, counting hidden ones (if **total** set to `true`, or `GEM_ITEMS_TOTAL`) or only visible (if **total** set to `false`, or `GEM_ITEMS_VISIBLE`). + +* *GEMItem** **getCurrentMenuItem()** + *Returns*: `GEMItem*` + Get pointer to currently focused menu item on this page. + +* *byte* **getCurrentMenuItemIndex()** + *Returns*: `byte` + Get index of currently focused menu item on this page. + +> **Note:** calls to methods that return a reference to the owning `GEMPage` object can be chained, e.g. `menuPageSettings.addMenuItem(menuItemInterval).addMenuItem(menuItemTempo).setParentMenuPage(menuPageMain);` (since GEM ver. 1.4.6). + ---------- ### GEMItem -Menu item of the menu. Can represent editable or read-only variable of type `int`, `byte`, `float`, `double`, `boolean`, `char[17]` (or `char[GEM_STR_LEN]`, to be exact); option select of type `int`, `byte`, `float`, `double`, `char[n]`; link to another menu page; or button that can invoke user-defined actions and create action-specific context, which can have its own enter (setup) and exit callbacks as well as loop function. User-defined callback function can be specified to invoke when editable menu item is saved or option is selected. Exact definition of `GEMItem` object depends on its type. +Menu item of the menu. Can represent editable or read-only variable of type `int`, `byte`, `float`, `double`, `bool`, `char[17]` (or `char[GEM_STR_LEN]`, to be exact); option select of type `int`, `byte`, `float`, `double`, `char[n]`; incremental spinner of type `int`, `byte`, `float`, `double`; link to another menu page; or button that can invoke user-defined actions and create action-specific context, which can have its own enter (setup) and exit callbacks as well as loop function. User-defined callback function can be specified to invoke when editable menu item is saved or option is selected. Exact definition of `GEMItem` object depends on its type. -> **Note:** support for editable variables of types `float` and `double` is optional. It is enabled by default, but can be disabled by editing [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) file that ships with the library. Disabling this feature may save considerable amount of program storage space (up to 10% on Arduino UNO). See [Floating-point variables](#floating-point-variables) for more details. +> **Note:** support for editable variables (and spinner) of types `float` and `double` is optional. It is enabled by default, but can be disabled by editing [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) file that ships with the library. Disabling this feature may save considerable amount of program storage space (up to 10% on Arduino UNO R3). See [Floating-point variables](#floating-point-variables) for more details. #### Variable @@ -1211,11 +1384,11 @@ GEMItem menuItemVar(title, linkedVariable[, saveCallback[, callbackVal]]); Title of the menu item displayed on the screen. * **linkedVariable** - *Type*: `int`, `byte`, `float`, `double`, `boolean`, `char[17]` (or `char[GEM_STR_LEN]`, to be exact) + *Type*: `int`, `byte`, `float`, `double`, `bool`, `char[17]` (or `char[GEM_STR_LEN]`, to be exact) Reference to variable that menu item is associated with. * **readonly** [*optional*] - *Type*: `boolean` + *Type*: `bool` *Values*: `GEM_READONLY` (alias for `true`), `false` *Default*: `false` Sets readonly mode for variable that menu item is associated with. @@ -1225,7 +1398,7 @@ GEMItem menuItemVar(title, linkedVariable[, saveCallback[, callbackVal]]); Pointer to callback function executed when associated variable is successfully saved. Optionally, callback function can expect argument of type `GEMCallbackData` to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified (see below). * **callbackVal** [*optional*] - *Type*: `int`, `byte`, `float`, `double`, `boolean`, `const char*`, `void*` + *Type*: `int`, `byte`, `float`, `double`, `bool`, `const char*`, `void*` *Default*: `0` Sets user-defined value of an argument that will be passed to callback function as a part of [`GEMCallbackData`](#gemcallbackdata) struct. @@ -1254,7 +1427,7 @@ GEMItem menuItemSelect(title, linkedVariable, select[, saveCallback[, callbackVa Reference to [`GEMSelect`](#gemselect) option select object that represents a list of available values. * **readonly** [*optional*] - *Type*: `boolean` + *Type*: `bool` *Values*: `GEM_READONLY` (alias for `true`), `false` *Default*: `false` Sets readonly mode for variable that menu item is associated with. @@ -1264,7 +1437,46 @@ GEMItem menuItemSelect(title, linkedVariable, select[, saveCallback[, callbackVa Pointer to callback function executed when associated variable is successfully saved. Optionally, callback function can expect argument of type `GEMCallbackData` to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified (see below). * **callbackVal** [*optional*] - *Type*: `int`, `byte`, `float`, `double`, `boolean`, `const char*`, `void*` + *Type*: `int`, `byte`, `float`, `double`, `bool`, `const char*`, `void*` + *Default*: `0` + Sets user-defined value of an argument that will be passed to callback function as a part of [`GEMCallbackData`](#gemcallbackdata) struct. + +> **Note:** you cannot specify both readonly mode and callback in the same constructor. However, you can set readonly mode for menu item with callback explicitly later using `GEMItem::setReadonly()` method. + +#### Spinner + +```cpp +GEMItem menuItemSpinner(title, linkedVariable, spinner[, readonly]); +``` +or +```cpp +GEMItem menuItemSpinner(title, linkedVariable, spinner[, saveCallback[, callbackVal]]); +``` + +* **title** + *Type*: `const char*` + Title of the menu item displayed on the screen. + +* **linkedVariable** + *Type*: `int`, `byte`, `float`, `double` + Reference to variable that menu item is associated with. + +* **spinner** + *Type*: `GEMSpinner` + Reference to [`GEMSpinner`](#gemspinner) spinner object that represents available range of values. + +* **readonly** [*optional*] + *Type*: `bool` + *Values*: `GEM_READONLY` (alias for `true`), `false` + *Default*: `false` + Sets readonly mode for variable that menu item is associated with. + +* **saveCallback** [*optional*] + *Type*: `pointer to function` + Pointer to callback function executed when associated variable is successfully saved. Optionally, callback function can expect argument of type `GEMCallbackData` to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified (see below). + +* **callbackVal** [*optional*] + *Type*: `int`, `byte`, `float`, `double`, `bool`, `const char*`, `void*` *Default*: `0` Sets user-defined value of an argument that will be passed to callback function as a part of [`GEMCallbackData`](#gemcallbackdata) struct. @@ -1285,7 +1497,7 @@ GEMItem menuItemLink(title, linkedPage[, readonly]); Menu page `GEMPage` that menu item is associated with. * **readonly** [*optional*] - *Type*: `boolean` + *Type*: `bool` *Values*: `GEM_READONLY` (alias for `true`), `false` *Default*: `false` Sets readonly mode for the link (user won't be able to navigate to linked page). @@ -1306,15 +1518,15 @@ GEMItem menuItemButton(title, buttonAction[, callbackVal[, readonly]]); * **buttonAction** *Type*: `pointer to function` - Pointer to function that will be executed when menu item is activated. Action-specific [context](#appcontext) can be created, which can have its own enter (setup) and exit callbacks as well as loop function. Optionally, callback function can expect argument of type `GEMCallbackData` to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified (see below). + Pointer to function that will be executed when menu item is activated. Action-specific [context](#gemcontext) can be created, which can have its own enter (setup) and exit callbacks as well as loop function. Optionally, callback function can expect argument of type `GEMCallbackData` to be passed to it when it is executed. In this case optional user-defined value of an argument can be specified (see below). * **callbackVal** [*optional*] - *Type*: `int`, `byte`, `float`, `double`, `boolean`, `const char*`, `void*` + *Type*: `int`, `byte`, `float`, `double`, `bool`, `const char*`, `void*` *Default*: `0` Sets user-defined value of an argument that will be passed to callback function as a part of [`GEMCallbackData`](#gemcallbackdata) struct. * **readonly** [*optional*] - *Type*: `boolean` + *Type*: `bool` *Values*: `GEM_READONLY` (alias for `true`), `false` *Default*: `false` Sets readonly mode for the button (user won't be able to call action associated with it). @@ -1340,52 +1552,69 @@ GEMItem menuItemButton(title, buttonAction[, callbackVal[, readonly]]); #### Methods -* **setCallbackVal(** _int_ | _byte_ | _float_ | _double_ | _boolean_ | _const char*_ | _void*_ callbackVal **)** - *Returns*: nothing +* *GEMItem&* **setCallbackVal(** _int_ | _byte_ | _float_ | _double_ | _bool_ | _const char*_ | _void*_ callbackVal **)** + *Returns*: `GEMItem&` Set user-defined value of an argument that will be passed to callback function as a part of [`GEMCallbackData`](#gemcallbackdata) struct. * *GEMCallbackData* **getCallbackData()** *Returns*: `GEMCallbackData` Get [`GEMCallbackData`](#gemcallbackdata) struct associated with menu item. It contains pointer to menu item and optionally user-defined value. -* **setTitle(** _const char*_ title **)** - *Returns*: nothing +* *GEMItem&* **setTitle(** _const char*_ title **)** + *Returns*: `GEMItem&` Set title of the menu item. Can be used to update menu item title dynamically. * *const char** **getTitle()** *Returns*: `const char*` Get title of the menu item. -* **setPrecision()** - *Returns*: nothing +* *GEMItem&* **setPrecision(** _byte_ prec **)** + *Accepts*: `byte` + *Returns*: `GEMItem&` Explicitly set precision for `float` or `double` variable as required by [`dtostrf()`](http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3ce8771d42) conversion used internally, i.e. the number of digits **after** the decimal sign. -* **setReadonly(** _boolean_ mode = true **)** - *Accepts*: `boolean` - *Returns*: nothing - Explicitly set (`setReadonly(true)`, or `setReadonly(GEM_READONLY)`, or `setReadonly()`) or unset (`setReadonly(false)`) readonly mode for variable that menu item is associated with (relevant for `GEM_VAL_INTEGER`, `GEM_VAL_BYTE`, `GEM_VAL_FLOAT`, `GEM_VAL_DOUBLE`, `GEM_VAL_CHAR`, `GEM_VAL_BOOLEAN` variable menu items and `GEM_VAL_SELECT` option select), or menu button `GEM_ITEM_BUTTON` and menu link `GEM_ITEM_LINK`, pressing of which won't result in any action, associated with them. +* *GEMItem&* **setAdjustedASCIIOrder(** _bool_ mode = true **)** + *Accepts*: `bool` + *Returns*: `GEMItem&` + Turn adjsuted order of characters when editing `char[17]` variables on (`setAdjustedASCIIOrder()`, or `setAdjustedASCIIOrder(true)`) or off (`setAdjustedASCIIOrder(false)`). When adjsuted order is enabled, space character is followed by "a" and preceded by "\`" (grave accent character). By default adjusted order is disabled, and characters follow order in ASCII table (space character is followed by "!"). Adjusted order can be more suitable for editing text variables in certain use cases (so user won't have to scroll past all of the special characters to get to alphabet). -* *boolean* **getReadonly()** - *Returns*: `boolean` +* *GEMItem&* **setReadonly(** _bool_ mode = true **)** + *Accepts*: `bool` + *Returns*: `GEMItem&` + Explicitly set (`setReadonly(true)`, or `setReadonly(GEM_READONLY)`, or `setReadonly()`) or unset (`setReadonly(false)`) readonly mode for variable that menu item is associated with (relevant for `int`, `byte`, `float`, `double`, `char[17]`, `bool` variable menu items and option select), or menu button and menu link, pressing of which won't result in any action, associated with them. + +* *bool* **getReadonly()** + *Returns*: `bool` Get readonly state of the variable that menu item is associated with (as well as menu link or button): `true` for readonly state, `false` otherwise. -* **hide(** _boolean_ hide = true **)** - *Accepts*: `boolean` - *Returns*: nothing +* *GEMItem&* **hide(** _bool_ hide = true **)** + *Accepts*: `bool` + *Returns*: `GEMItem&` Hide (`hide(true)`, or `hide(GEM_HIDDEN)`, or `hide()`) or show (`hide(false)`) menu item. Hidden menu items won't be printed to the screen the next time menu is drawn. -* **show()** - *Returns*: nothing +* *GEMItem&* **show()** + *Returns*: `GEMItem&` Show previously hidden menu item. -* *boolean* **getHidden()** - *Returns*: `boolean` +* *bool* **getHidden()** + *Returns*: `bool` Get hidden state of the menu item: `true` when menu item is hidden, `false` otherwise. +* *GEMItem&* **remove()** + *Returns*: `GEMItem&` + Remove menu item from parent menu page. Unlike `hide()`, completely detaches menu item from menu page. Removed menu item then can be added to the same or a different menu page (via `GEMPage::addMenuItem()`) or even safely destroyed. Effectively the opposite of `GEMPage::addMenuItem()` method. + * *void** **getLinkedVariablePointer()** *Returns*: `void*` Get pointer to a linked variable (relevant for menu items that represent variable). Note that user is reponsible for casting `void*` pointer to a correct pointer type. +* *GEMItem** **getMenuItemNext(** _bool_ total = false **)** + *Accepts*: `bool` + *Returns*: `GEMItem*` + Get pointer to next menu item, i.e. menu item that follows this one on the menu page, including hidden ones (if **total** set to `true`, or `GEM_ITEMS_TOTAL`) or only visible (if **total** set to `false`, or `GEM_ITEMS_VISIBLE`). + +> **Note:** calls to methods that return a reference to the owning `GEMItem` object can be chained, e.g. `menuItemInterval.setReadonly().show();` (since GEM ver. 1.4.6). + ---------- @@ -1510,9 +1739,145 @@ SelectOptionChar selectOption = {name, val_char}; ---------- +### GEMSpinner + +Spinner is similar to option select, but instead of specifying available options as an array, requires setting of minimum and maximum values of available range and a step with which increment/decrement of the linked variable is performed. Available since GEM ver. 1.6. + +`GEMSpinner` represents range of values available for incremental spinner. Supplied to `GEMItem` constructor. Object of class `GEMSpinner` defines as follows: + +```cpp +GEMSpinner mySpinner(boundaries); +``` + +* **boundaries** + *Type*: `GEMSpinnerBoundariesInt`, or `GEMSpinnerBoundariesByte`, or `GEMSpinnerBoundariesFloat`, or `GEMSpinnerBoundariesDouble` + Settings of the incremental spinner, such as minimum and maximum boundaries of available values in range, and step with which increment/decrement of value is performed. Type of boundaries object is either `GEMSpinnerBoundariesInt`, or `GEMSpinnerBoundariesByte`, or `GEMSpinnerBoundariesFloat`, or `GEMSpinnerBoundariesDouble` depending on the type of variable the spinner is associated with. See the following section for definition of these custom types. + +Example of use: + +```cpp +// Integer spinner +// 1) Define settings (boundaries) of the spinner: +GEMSpinnerBoundariesInt mySpinnerBoundaries = { .step = 50, .min = -150, .max = 150 }; +// 2) Supply settings to GEMSpinner constructor: +GEMSpinner mySpinner(mySpinnerBoundaries); +``` + +It is possible to exclude support for spinner menu items to save some space on your chip. For that, locate file [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) that comes with the library, open it and comment out corresponding inclusion, i.e. change this line: + +```cpp +#include "config/support-spinner.h" +``` + +to + +```cpp +// #include "config/support-spinner.h" +``` + +> Keep in mind that contents of the `config.h` file most likely will be reset to its default state after installing library update. + +Or, alternatively, define `GEM_DISABLE_SPINNER` flag before build. E.g. in [PlatformIO](https://platformio.org/) environment via `platformio.ini`: + +```ini +build_flags = + ; Disable support for increment/decrement spinner menu items + -D GEM_DISABLE_SPINNER +``` + + +---------- + + +### GEMSpinnerBoundariesInt + +Data structure that represents settings of the spinner of type `int`. Object of type `GEMSpinnerBoundariesInt` defines as follows: + +```cpp +GEMSpinnerBoundariesInt boundaries = {step, min, max}; +``` + +* **step** + *Type*: `int` + Step with which increment/decrement of the spinner value is performed. + +* **min** + *Type*: `int` + Minimum boundary of the spinner range. + +* **max** + *Type*: `int` + Maximum boundary of the spinner range. + +### GEMSpinnerBoundariesByte + +Data structure that represents settings of the spinner of type `byte`. Object of type `GEMSpinnerBoundariesByte` defines as follows: + +```cpp +GEMSpinnerBoundariesByte boundaries = {step, min, max}; +``` + +* **step** + *Type*: `byte` + Step with which increment/decrement of the spinner value is performed. + +* **min** + *Type*: `byte` + Minimum boundary of the spinner range. + +* **max** + *Type*: `byte` + Maximum boundary of the spinner range. + +### GEMSpinnerBoundariesFloat + +Data structure that represents settings of the spinner of type `float`. Object of type `GEMSpinnerBoundariesFloat` defines as follows: + +```cpp +GEMSpinnerBoundariesFloat boundaries = {step, min, max}; +``` + +* **step** + *Type*: `float` + Step with which increment/decrement of the spinner value is performed. + +* **min** + *Type*: `float` + Minimum boundary of the spinner range. + +* **max** + *Type*: `float` + Maximum boundary of the spinner range. + +### GEMSpinnerBoundariesDouble + +Data structure that represents settings of the spinner of type `double`. Object of type `GEMSpinnerBoundariesDouble` defines as follows: + +```cpp +GEMSpinnerBoundariesDouble boundaries = {step, min, max}; +``` + +* **step** + *Type*: `double` + Step with which increment/decrement of the spinner value is performed. + +* **min** + *Type*: `double` + Minimum boundary of the spinner range. + +* **max** + *Type*: `double` + Maximum boundary of the spinner range. + +> **Note:** It is up to author of the sketch to make sure that initial value of the associated variable is within allowable range of spinner, and that type of variable and types of step and min/max boundaries match (and their values don't exceed capacity of their data type). Increment/decrement of variable will stop closest to the corresponding min/max boundary of allowable range, and result variable value may not always reach said boundaries exactly, if initial value and step combination won't allow it. If initial value is not within min/max boundaries interaction with spinner won't affect it (it will be possible to enter edit mode but won't be possible to increment/decrement value). If step is supplied as a negative value, the absolute value will be taken instead. If supplied value of min is greater than max, min/max values will be swapped. So `{ .step = -50, .min = 150, .max = 49 }` is equivalent to `{ .step = 50, .min = 49, .max = 150 }`. + + +---------- + + ### GEMCallbackData -Data structure that represents an argument that optionally can be passed to callback function associated with menu item. It contains pointer to menu item itself and a user-defined value, which can be one of the following types: `int`, `byte`, `float`, `double`, `boolean`, `const char*`, `void*`. The value is stored as an anonymous union, so choose carefully which property to use to access it (as it is will access the same portion of memory). +Data structure that represents an argument that optionally can be passed to callback function associated with menu item. It contains pointer to menu item itself and a user-defined value, which can be one of the following types: `int`, `byte`, `float`, `double`, `bool`, `const char*`, `void*`. The value is stored as an anonymous union, so choose carefully which property to use to access it (as it is will access the same portion of memory). Declaration of `GEMCallbackData` type: @@ -1524,7 +1889,7 @@ struct GEMCallbackData { int valInt; float valFloat; double valDouble; - boolean valBoolean; + bool valBoolean; bool valBool; const char* valChar; void* valPointer; @@ -1555,8 +1920,8 @@ Object of type `GEMCallbackData` contains the following properties: User-defined value of type `double` as a part of an anonymous union. * **valBoolean** (part of a union) - *Type*: `boolean` - User-defined value of type `boolean` as a part of an anonymous union (the same as `valBool` property). + *Type*: `bool` + User-defined value of type `bool` as a part of an anonymous union (the same as `valBool` property). * **valBool** (part of a union) *Type*: `bool` @@ -1596,16 +1961,116 @@ For more details and examples of using user-defined callback arguments see corre ---------- -### AppContext +### GEMAppearance + +Data structure that holds values of appearance settings that define how menu is rendered on screen. Can be submitted to `GEM` (`GEM_u8g2`, `GEM_adafruit_gfx`) constructor instead of specifying each option as a separate argument, or passed as an argument to `GEM::setAppearance()` (`GEM_u8g2::setAppearance()`, `GEM_adafruit_gfx::setAppearance()`) method to set general appearance of the menu (that will be used for every menu page if not overridden). Pointer to object of type `GEMAppearance` can be passed to `GEMPage::setAppearance()` method to customize appearance of corresponding menu page individually. + +Object of type `GEMAppearance` defines as follows: + +```cpp +GEMAppearance appearanceGeneral = {menuPointerType, menuItemsPerScreen, menuItemHeight, menuPageScreenTopOffset, menuValuesLeftOffset} +``` -Data structure that represents "context" of the currently executing user action, toggled by pressing menu item button. Property `context` of the `GEM` (`GEM_u8g2`, `GEM_adafruit_gfx`) object is of type `AppContext`. +* **menuPointerType** + *Type*: `byte` + *Values*: `GEM_POINTER_ROW`, `GEM_POINTER_DASH` + Type of menu pointer visual appearance: either highlighted row or pointer to the left of the row. + +* **menuItemsPerScreen** + *Type*: `byte` + *Values*: number, `GEM_ITEMS_COUNT_AUTO` (alias for `0`) + Count of the menu items per screen. Suitable for 128x64 screen with other variables at their default values. If set to `GEM_ITEMS_COUNT_AUTO`, the number of menu items will be determined automatically based on actual height of the screen. + +* **menuItemHeight** + *Type*: `byte` + *Units*: dots + Height of the menu item. Suitable for 128x64 screen with other variables at their default values. + +* **menuPageScreenTopOffset** + *Type*: `byte` + *Units*: dots + Offset from the top of the screen to accommodate title of the menu page. Suitable for 128x64 screen with other variables at their default values. + +* **menuValuesLeftOffset** + *Type*: `byte` + *Units*: dots + Offset from the left of the screen to the value of the associated with menu item variable (effectively the space left for the title of the menu item to be printed on screen). Suitable for 128x64 screen with other variables at their default values; 86 - recommended value for 128x64 screen. + +Basic example of use: + +```cpp +// Create GEMAppearance object with general values (that will be used for every menu page if not overridden) +GEMAppearance appearanceGeneral = {/* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86} +// Create GEMAppearance object as a copy of appearanceGeneral (its values can be customized later in sketch). +// Note that it should be created in a global scope of the sketch (in order to be passed as a pointer to menu page) +GEMAppearance appearanceSettings = appearanceGeneral; + +// Create menu object (and pass appearanceGeneral as an argument to constructor) +GEM menu(glcd, appearanceGeneral); + +... + +// Later in sketch, e.g. in setupMenu() +void setupMenu() { + ... + appearanceSettings.menuValuesLeftOffset = 70; + menuPageSettings.setAppearance(&appearanceSettings); // Note `&` operator + ... +} +``` + +Alternatively: + +```cpp +// Create empty GEMAppearance object (its values can be populated later in sketch). +// Note that it should be created in a global scope of the sketch (in order to be passed as a pointer to menu page) +GEMAppearance appearanceSettings; + +// Create menu object (its appearance settings will be populated with default values) +GEM menu(glcd); + +... + +// Later in sketch, e.g. in setupMenu() +void setupMenu() { + ... + // Create GEMAppearance object with general values (that will be used for every menu page if not overridden) + GEMAppearance appearanceGeneral; + appearanceGeneral.menuPointerType = GEM_POINTER_ROW; + appearanceGeneral.menuItemsPerScreen = GEM_ITEMS_COUNT_AUTO; + appearanceGeneral.menuItemHeight = 10; + appearanceGeneral.menuPageScreenTopOffset = 10; + appearanceGeneral.menuValuesLeftOffset = 86; + + // Set appearanceGeneral as a general appearance of the menu + menu.setAppearance(appearanceGeneral); // Note there is no `&` operator when setting general (or global) appearance of the menu + + // Copy values from appearanceGeneral object to appearanceSettings for further customization + appearanceSettings = appearanceGeneral; + appearanceSettings.menuValuesLeftOffset = 70; + menuPageSettings.setAppearance(&appearanceSettings); // Note `&` operator + ... +} +``` + +Passing `GEMAppearance` object to `GEMPage::setAppearance()` method as a pointer allows to change appearance of the _individual menu page_ dynamically by changing values stored in object (and making sure that `menu.drawMenu();` is called afterwards) without need for additional call to `GEMPage::setAppearance()`. In contrast, to change _general appearance_ of the menu (and not individual page) `menu.setAppearance(appearanceGeneral);` method should be called with new or updated `GEMAppearance` object supplied as an argument (and `menu.drawMenu();` should be called afterwards as well). + +For more details about appearance customization see corresponding section of the [wiki](https://github.com/Spirik/GEM/wiki). + + +---------- + + +### GEMContext + +Data structure that represents "context" of currently executing user action, toggled by pressing menu item button. Property `context` of the `GEM` (`GEM_u8g2`, `GEM_adafruit_gfx`) object is of type `GEMContext`. Consists of pointers to user-supplied functions that represent setup and loop functions (named `context.enter()` and `context.loop()` respectively) of the context. It allows you to initialize variables and e.g. prepare screen (if needed for the task that function performs), and then run through loop function, waiting for user input, or sensor reading, or command to terminate and exit back to the menu eventually. In the latter case additional `context.exit()` function will be called, that can be used to clean up your context and e.g. to free some memory and draw menu back to screen. -Object of type `AppContext` defines as follows: +Object of type `GEMContext` (also aliased as `AppContext`) defines as follows: ```cpp -AppContext myContext = {loop, enter, exit, allowExit}; +GEMContext myContext = {loop, enter, exit, allowExit}; ``` * **loop** @@ -1621,7 +2086,7 @@ AppContext myContext = {loop, enter, exit, allowExit}; Pointer to `exit()` function of current context. Called automatically when user exits currently running context if `context.allowExit` (see below) is set to `true`. Should be invoked manually otherwise. Usually contains instructions to do some cleanup after context's `loop()` and to draw menu on screen again (by calling `drawMenu()` method of `GEM`, `GEM_u8g2` or `GEM_adafruit_gfx` object). If no user-defined function specified, default action will be invoked that consists of call to three methods of `GEM`, `GEM_u8g2` or `GEM_adafruit_gfx` object: `reInit()`, `drawMenu()`, and `clearContext()`. * **allowExit** - *Type*: `boolean` + *Type*: `bool` *Default*: `true` Setting to `false` will require manually exit the context's `loop()` from within the loop itself: all necessary key detection should be done in context's `loop()` accordingly, and `context.exit()` should be called explicitly; otherwise exit is handled automatically by pressing `GEM_KEY_CANCEL` key. @@ -1674,7 +2139,6 @@ void buttonContextExit() { // Clear context (assigns `nullptr` values to function pointers of the `context` property of the `GEM` object and resets `allowExit` flag to its default state) menu.clearContext(); } - ``` To exit currently running context and return to menu, press button associated with `GEM_KEY_CANCEL` key (only if `context.allowExit` flag is set to its default value of `true`, otherwise you should handle exit from the loop manually and call `context.exit()` explicitly) - `context.exit()` callback will be called. @@ -1683,7 +2147,7 @@ For more details see supplied example on context usage and read corresponding se Floating-point variables ----------- -The [`float`](https://www.arduino.cc/reference/en/language/variables/data-types/float/) data type has only 6-7 decimal digits of precision ("[mantissa](https://en.wikipedia.org/wiki/Scientific_notation)"). For AVR based Arduino boards (like UNO) [`double`](https://www.arduino.cc/reference/en/language/variables/data-types/double/) data type has basically the same precision, being only 32 bit wide (the same as `float`). On some other boards (like SAMD boards, e.g. with M0 chips) double is actually a 64 bit number, so it has more precision (up to 15 digits). +The [`float`](https://www.arduino.cc/reference/en/language/variables/data-types/float/) data type has only 6-7 decimal digits of precision ("[mantissa](https://en.wikipedia.org/wiki/Scientific_notation)"). For AVR based Arduino boards (like UNO R3) [`double`](https://www.arduino.cc/reference/en/language/variables/data-types/double/) data type has basically the same precision, being only 32 bit wide (the same as `float`). On some other boards (like SAMD boards, e.g. with M0 chips) double is actually a 64 bit number, so it has more precision (up to 15 digits). Internally in GEM, [`dtostrf()`](http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3ce8771d42) and [`atof()`](http://www.cplusplus.com/reference/cstdlib/atof/) are used to convert floating-point number to and from a string. Support for `dtostrf()` comes with `stdlib.h` for AVR, and hence available out of the box for AVR-based boards. While it is possible to use [`sprintf()`](http://www.cplusplus.com/reference/cstdio/sprintf/) for some other boards (like SAMD), `dtostrf()` is used for them instead as well, for consistency through explicit inclusion of `avr/dtostrf.h`. See [this thread](https://github.com/plotly/arduino-api/issues/38#issuecomment-108987647) for some more details on `dtostrf()` support across different boards. @@ -1691,7 +2155,7 @@ Default precision (the number of digits **after** the decimal sign, in terms of Note that maximum length of the number should not exceed `GEM_STR_LEN` (i.e. 17) - otherwise overflows and undetermined behavior may occur (that includes the value of precision specified through `GEMItem::setPrecision()` method or default one, which will increase length of the number with trailing zeros if necessary). This is result of using `char[GEM_STR_LEN]` buffer during `dtostrf()` conversion. It is not possible to enter number with the length exceeding this limit during edit of the variable, however, additional caution should be taken to verify that initial value of the variable (or externally changed value) in combination with specified precision does not exceed this limit. -It is possible to exclude support for editable `float` and `double` variables to save some space on your chip (up to 10% of program storage space on UNO). For that, locate file [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) that comes with the library, open it and comment out corresponding inclusion, i.e. change this line: +It is possible to exclude support for editable `float` and `double` variables to save some space on your chip (up to 10% of program storage space on UNO R3). For that, locate file [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) that comes with the library, open it and comment out corresponding inclusion, i.e. change this line: ```cpp #include "config/support-float-edit.h" @@ -1715,37 +2179,69 @@ build_flags = Note that option selects support `float` and `double` variables regardless of this setting. +Advanced Mode +----------- +Advanced Mode provides additional means to modify, customize and extend functionality of GEM. + +When Advanced Mode is enabled some of the internal methods of the library is made `virtual` (marked with `GEM_VIRTUAL` macro in source code). That (alongside with `public` and `protected` access specifiers) makes it possible to override those methods in your own sketch. However keep in mind that inner workings of GEM is more prone to change than its public interface (e.g. during code refactoring), so be cautious to upgrade GEM version if your code is relying on derivative classes and overrides. + +Additional features of Advanced Mode may be added in the future. + +To enable Advanced Mode, locate file [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) that comes with the library, open it and comment out the following line: + +```cpp +#define GEM_DISABLE_ADVANCED_MODE +``` + +to + +```cpp +// #define GEM_DISABLE_ADVANCED_MODE +``` + +> Keep in mind that contents of the `config.h` file most likely will be reset to its default state after installing library update. + +Or, alternatively, define `GEM_ENABLE_ADVANCED_MODE` flag before build. E.g. in [PlatformIO](https://platformio.org/) environment via `platformio.ini`: + +```ini +build_flags = + ; Enable Advanced Mode + -D GEM_ENABLE_ADVANCED_MODE +``` + +Note that GEM in Advanced Mode requires more memory to run, so plan accordingly. + Configuration ----------- It is possible to configure GEM library by excluding some features not needed in your project. That may help to save some additional program storage space. E.g., you can disable support for editable floating-point variables (see previous [section](#floating-point-variables)). You can also choose which version of GEM library (`AltSerialGraphicLCD`, `U8g2` or `Adafruit GFX` based) should be compiled. That way, there won't be requirement to have all of the supported graphics libraries installed in the system at the same time (regardless of which one is actually used). -Currently there are two ways of achieving that. +Currently there are two ways of achieving that. ### Manual `config.h` edition For that, locate file [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) that comes with the library, open it and comment out corresponding inclusion. -To disable `AltSerialGraphicLCD` support comment out the following line: +Support for `AltSerialGraphicLCD` is disabled by default since GEM ver. 1.5. To _enable_ `AltSerialGraphicLCD` support comment out the following line: ```cpp -#include "config/enable-glcd.h" +#define GEM_DISABLE_GLCD ``` -To disable `U8g2` support comment out the following line: +To _disable_ `U8g2` support comment out the following line: ```cpp #include "config/enable-u8g2.h" ``` -To disable `Adafruit GFX` support comment out the following line: +To _disable_ `Adafruit GFX` support comment out the following line: ```cpp #include "config/enable-adafruit-gfx.h" ``` -More configuration options may be be added in the future. +More configuration options may be added in the future. > Keep in mind that contents of the `config.h` file most likely will be reset to its default state after installing library update. @@ -1755,8 +2251,8 @@ Alternatively, define corresponding flag before build. E.g. in [PlatformIO](http ```ini build_flags = - ; Disable AltSerialGraphicLCD support - -D GEM_DISABLE_GLCD + ; Enable AltSerialGraphicLCD support + -D GEM_ENABLE_GLCD ; Disable U8g2 support -D GEM_DISABLE_U8G2 ; Disable Adafruit GFX support @@ -1765,9 +2261,11 @@ build_flags = Compatibility ----------- -ESP32 and ESP8266 based boards are not supported in AltSerialGraphicLCD version of GEM: this library should be commented out in [config.h](https://github.com/Spirik/GEM/blob/master/src/config.h) before compiling. +Some boards (e.g. ESP32, ESP8266, RP2040, nRF52840, etc. based boards) are not supported in AltSerialGraphicLCD version of GEM: this library should not be enabled in [Configuration](#configuration) before compiling for these boards. + +When support for [Floating-point variables](#floating-point-variables) is enabled, GEM relies on `dtostrf()` function to handle conversion to a string, which may not be available for all of the architectures supported by Arduino by default. You may have to manually include support for it, e.g., via explicit inclusion of suitable version of `dtostrf.h` header file in `GEM.cpp`, `GEM_u8g2.cpp` or `GEM_adafruit_gfx.cpp` source files. It is available for AVR-based boards by default and currently it is explicitly included for SAMD boards (e.g. with M0 chips), RP2040 and nRF52840 based boards. ESP32 based boards should be fine as well. -When support for [Floating-point variables](#floating-point-variables) is enabled, GEM relies on `dtostrf()` function to handle conversion to a string, which may not be available for all of the architectures supported by Arduino by default. You may have to manually include support for it, e.g., via explicit inclusion of suitable version of `dtostrf.h` header file in `GEM.cpp`, `GEM_u8g2.cpp` or `GEM_adafruit_gfx.cpp` source files. It is available for AVR-based boards by default and currently it is explicitly included for SAMD boards (e.g. with M0 chips). ESP32-based boards should be fine as well. +> **Note:** there are reports of possible compatibility issues with some ESP32 based boards resulting in `flash read err, 1000` message after flashing compiled sketch (some users find it is possible to reflash the board afterwards to restore its functionality, some don't). Check this [thread](https://github.com/Spirik/GEM/issues/55) for more details. Examples ----------- diff --git a/examples/AdafruitGFX/Example-01_Basic/Example-01_Basic.ino b/examples/AdafruitGFX/Example-01_Basic/Example-01_Basic.ino index 604caf0..3d5f05f 100644 --- a/examples/AdafruitGFX/Example-01_Basic/Example-01_Basic.ino +++ b/examples/AdafruitGFX/Example-01_Basic/Example-01_Basic.ino @@ -1,8 +1,8 @@ /* Basic menu example using GEM library. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, - and a button, pressing of which will result in int variable value printed to Serial monitor if boolean variable is set to true. + Simple one page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. Adafruit GFX library is used to draw menu. KeyDetector library is used to detect push-buttons presses. @@ -35,7 +35,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Macro constants (aliases) for the pins TFT display is connected to. Please update the pin numbers according to your setup #define TFT_CS A2 @@ -51,7 +51,7 @@ Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // Create variables that will be editable through the menu and assign them initial values int number = -512; -boolean enablePrint = false; +bool enablePrint = false; // Create two menu item objects of class GEMItem, linked to number and enablePrint variables GEMItem menuItemInt("Number:", number); @@ -69,6 +69,8 @@ GEMPage menuPageMain("Main Menu"); // Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to tft object we created earlier GEM_adafruit_gfx menu(tft, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_adafruit_gfx menu(tft, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes diff --git a/examples/AdafruitGFX/Example-02_Blink/Example-02_Blink.ino b/examples/AdafruitGFX/Example-02_Blink/Example-02_Blink.ino index 968a8e2..02563a5 100644 --- a/examples/AdafruitGFX/Example-02_Blink/Example-02_Blink.ino +++ b/examples/AdafruitGFX/Example-02_Blink/Example-02_Blink.ino @@ -40,7 +40,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Macro constants (aliases) for the pins TFT display is connected to. Please update the pin numbers according to your setup #define TFT_CS A2 @@ -63,7 +63,7 @@ char label[GEM_STR_LEN] = "Blink!"; // Maximum length of the string should not e unsigned long previousMillis = 0; // Variable to hold current label state (visible or hidden) -boolean labelOn = false; +bool labelOn = false; // Create two menu item objects of class GEMItem, linked to interval and label variables // with validateInterval() callback function attached to interval menu item, @@ -93,6 +93,8 @@ GEMItem menuItemMainSettings("Settings", menuPageSettings); // Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to tft object we created earlier GEM_adafruit_gfx menu(tft, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_adafruit_gfx menu(tft, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes diff --git a/examples/AdafruitGFX/Example-03_Party-Hard/Example-03_Party-Hard.ino b/examples/AdafruitGFX/Example-03_Party-Hard/Example-03_Party-Hard.ino index 90bfbc6..798708c 100644 --- a/examples/AdafruitGFX/Example-03_Party-Hard/Example-03_Party-Hard.ino +++ b/examples/AdafruitGFX/Example-03_Party-Hard/Example-03_Party-Hard.ino @@ -3,7 +3,7 @@ editable menu items with validation callbacks, setting readonly mode, creation of context with context.allowExit set to false in order to use push-buttons to control scene within context's loop routine. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, one option select, + Simple one page menu with one editable menu item associated with int variable, one with bool variable, one option select, and a button, pressing of which will launch an animation sequence drawn to the screen. Delay between frames is determined by value of int variable, setting of which to 0 will enable manual control of the frames through navigation push-buttons. @@ -43,7 +43,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Macro constants (aliases) for the pins TFT display is connected to. Please update the pin numbers according to your setup #define TFT_CS A2 @@ -59,7 +59,7 @@ Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // Create variables that will be editable through the menu and assign them initial values int interval = 200; -boolean strobe = false; +bool strobe = false; // Create variable that will be editable through option select and create associated option select byte tempo = 0; @@ -97,6 +97,8 @@ GEMPage menuPageMain("Party Hard"); // Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to tft object we created earlier GEM_adafruit_gfx menu(tft, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_adafruit_gfx menu(tft, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes @@ -177,7 +179,7 @@ void applyTempo() { menuItemInt.setReadonly(false); } // Print tempo variable to Serial - Serial.print("Tempo option: "); + Serial.print("Tempo: "); Serial.println(tempo); } @@ -191,7 +193,7 @@ void drawSprite(Splash _splash, byte _mode) { } // Draw frame based on direction of animation -void drawFrame(boolean forward) { +void drawFrame(bool forward) { if (forward) { // Next frame currentFrame = (currentFrame == framesCount ? 1 : currentFrame+1); diff --git a/examples/AdafruitGFX/Example-04_Blink_Recolor/Example-04_Blink_Recolor.ino b/examples/AdafruitGFX/Example-04_Blink_Recolor/Example-04_Blink_Recolor.ino index 6814570..355f8db 100644 --- a/examples/AdafruitGFX/Example-04_Blink_Recolor/Example-04_Blink_Recolor.ino +++ b/examples/AdafruitGFX/Example-04_Blink_Recolor/Example-04_Blink_Recolor.ino @@ -42,7 +42,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Macro constants (aliases) for the pins TFT display is connected to. Please update the pin numbers according to your setup #define TFT_CS A2 @@ -65,7 +65,7 @@ char label[GEM_STR_LEN] = "Blink!"; // Maximum length of the string should not e unsigned long previousMillis = 0; // Variable to hold current label state (visible or hidden) -boolean labelOn = false; +bool labelOn = false; // Variables to hold currently set foreground and background colors int foreColor = ST77XX_WHITE; @@ -108,6 +108,8 @@ GEMItem menuItemMainSettings("Settings", menuPageSettings); // Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to tft object we created earlier GEM_adafruit_gfx menu(tft, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_adafruit_gfx menu(tft, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes diff --git a/examples/AdafruitGFX/Example-05_Encoder/Example-05_Encoder.ino b/examples/AdafruitGFX/Example-05_Encoder/Example-05_Encoder.ino new file mode 100644 index 0000000..9ba9a7c --- /dev/null +++ b/examples/AdafruitGFX/Example-05_Encoder/Example-05_Encoder.ino @@ -0,0 +1,276 @@ +/* + Basic menu example using GEM library. Using rotary encoder as an input source. + + Simple two page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. + + Second menu page with option select is added to better demonstrate operation of the menu with rotary encoder. + + Adafruit GFX library is used to draw menu. + KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. + + Additional info (including the breadboard view) available on GitHub: + https://github.com/Spirik/GEM + + This example code is in the public domain. +*/ + +#include +#include + +// SPI and I2C libraries required by SH1106 display +//#include +#include +#include + +// Hardware-specific library for SH1106. +// Include library that matches your setup (see https://learn.adafruit.com/adafruit-gfx-graphics-library for details) +#include + +// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button) +#define KEY_A 1 +#define KEY_B 2 +#define KEY_C 3 + +// Pins encoder is connected to +const byte channelA = 2; +const byte channelB = 3; +const byte buttonPin = 4; + +byte chanB = HIGH; // Variable to store Channel B readings + +// Array of Key objects that will link GEM key identifiers with dedicated pins +// (it is only necessary to detect signal change on a single channel of the encoder, either A or B; +// order of the channel and push-button Key objects in an array is not important) +Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}}; +//Key keys[] = {{KEY_C, buttonPin}, {KEY_A, channelA}}; + +// Create KeyDetector object +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); +// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay +// as the third argument to KeyDetector constructor. +// Make sure to adjust debounce delay to better fit your rotary encoder. +// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case). +// Analog threshold is not necessary for this example and is set to default value 16. +KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 5, /* analogThreshold= */ 16, /* pullup= */ true); + +bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers +bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times +const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms +long keyPressTime = 0; // Variable to hold time of the key press event +long now; // Variable to hold current time taken with millis() function at the beginning of loop() + +/* Uncomment to initialize the I2C address, uncomment only one, if you get a totally blank screen try the other */ +#define i2c_Address 0x3c // Initialize with the I2C addr 0x3C Typically eBay OLED's +//#define i2c_Address 0x3d // Initialize with the I2C addr 0x3D Typically Adafruit OLED's + +// Macro constants (aliases) for display setup +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_RESET -1 // QT-PY / XIAO + +// Create an instance of the Adafruit GFX library. +// Use constructor that matches your setup (see https://learn.adafruit.com/adafruit-gfx-graphics-library for details). +// SH1106 based display is used in the example. +// This instance is used to call all the subsequent Adafruit GFX functions (internally from GEM library, +// or manually in your sketch if it is required) +Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +// Create variables that will be editable through the menu and assign them initial values +int number = -512; +bool enablePrint = false; + +// Create variable that will be editable through option select and create associated option select. +// This variable will be passed to menu.invertKeysDuringEdit(), and naturally can be presented as a bool, +// but is declared as a byte type to be used in an option select rather than checkbox (for demonstration purposes) +byte invert = 1; +SelectOptionByte selectInvertOptions[] = {{"Invert", 1}, {"Normal", 0}}; +GEMSelect selectInvert(sizeof(selectInvertOptions)/sizeof(SelectOptionByte), selectInvertOptions); + +// Create menu item for option select with applyInvert() callback function +void applyInvert(); // Forward declaration +GEMItem menuItemInvert("Chars order:", invert, selectInvert, applyInvert); + +// Create two menu item objects of class GEMItem, linked to number and enablePrint variables +GEMItem menuItemInt("Number:", number); +GEMItem menuItemBool("Enable print:", enablePrint); + +// Create menu button that will trigger printData() function. It will print value of our number variable +// to Serial monitor if enablePrint is true. We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void printData(); // Forward declaration +GEMItem menuItemButton("Print", printData); + +// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. +// Menu can have multiple menu pages (linked to each other) with multiple menu items each +GEMPage menuPageMain("Main Menu"); // Main page +GEMPage menuPageSettings("Settings"); // Settings submenu + +// Create menu item linked to Settings menu page +GEMItem menuItemMainSettings("Settings", menuPageSettings); + +// Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to display object we created earlier +GEM_adafruit_gfx menu(display, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_adafruit_gfx menu(display, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); + +void setup() { + // Pin modes + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + pinMode(buttonPin, INPUT_PULLUP); + + // Serial communication setup + Serial.begin(115200); + + // Show image buffer on the display hardware. + // Since the buffer is intialized with an Adafruit splashscreen + // internally, this will display the splashscreen. + delay(250); // Wait for the OLED to power up + display.begin(i2c_Address, true); // Address 0x3C default + + display.display(); + delay(2000); + + // Clear the buffer + display.clearDisplay(); + + // Explicitly set correct colors for monochrome OLED screen + menu.setForegroundColor(SH110X_WHITE); + menu.setBackgroundColor(SH110X_BLACK); + + // Disable GEM splash (it won't be visible on the screen of buffer-equiped displays such as this one any way) + menu.setSplashDelay(0); + + // Turn inverted order of characters during edit mode on (feels more natural when using encoder) + menu.invertKeysDuringEdit(invert); + + // Menu init, setup and draw + menu.init(); + setupMenu(); + menu.drawMenu(); + + display.display(); + + Serial.println("Initialized"); +} + +void setupMenu() { + // Add menu items to Settings menu page + menuPageSettings.addMenuItem(menuItemInvert); + + // Add menu items to menu page + menuPageMain.addMenuItem(menuItemMainSettings); + menuPageMain.addMenuItem(menuItemInt); + menuPageMain.addMenuItem(menuItemBool); + menuPageMain.addMenuItem(menuItemButton); + + // Specify parent menu page for the Settings menu page + menuPageSettings.setParentMenuPage(menuPageMain); + + // Add menu page to menu and set it as current + menu.setMenuPageCurrent(menuPageMain); +} + +void loop() { + // Get current time to use later on + now = millis(); + + // If menu is ready to accept button press... + if (menu.readyForKey()) { + chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm + // ...detect key press using KeyDetector library + // and pass pressed button to menu + myKeyDetector.detect(); + + switch (myKeyDetector.trigger) { + case KEY_A: + // Signal from Channel A of encoder was detected + if (chanB == LOW) { + // If channel B is low then the knob was rotated CCW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... + Serial.println("Rotation CCW with button pressed"); + menu.registerKeyPress(GEM_KEY_LEFT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_UP otherwise + Serial.println("Rotation CCW"); + menu.registerKeyPress(GEM_KEY_UP); + } + } else { + // If channel B is high then the knob was rotated CW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... + Serial.println("Rotation CW with button pressed"); + menu.registerKeyPress(GEM_KEY_RIGHT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_DOWN otherwise + Serial.println("Rotation CW"); + menu.registerKeyPress(GEM_KEY_DOWN); + } + } + break; + case KEY_C: + // Button was pressed + Serial.println("Button pressed"); + // Save current time as a time of the key press event + keyPressTime = now; + break; + } + switch (myKeyDetector.triggerRelease) { + case KEY_C: + // Button was released + Serial.println("Button released"); + if (!secondaryPressed) { + // If button was not used as a modifier to rotation action... + if (now <= keyPressTime + keyPressDelay) { + // ...and if not enough time passed since keyPressTime, + // treat key that was pressed as Ok button + menu.registerKeyPress(GEM_KEY_OK); + } + } + secondaryPressed = false; + cancelPressed = false; + break; + } + // After keyPressDelay passed since keyPressTime + if (now > keyPressTime + keyPressDelay) { + switch (myKeyDetector.current) { + case KEY_C: + if (!secondaryPressed && !cancelPressed) { + // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet + Serial.println("Button remained pressed"); + // Treat key that was pressed as Cancel button + menu.registerKeyPress(GEM_KEY_CANCEL); + cancelPressed = true; + } + break; + } + } + + // Necessary to actually draw current state of the menu on screen + display.display(); + } +} + +void printData() { + // If enablePrint flag is set to true (checkbox on screen is checked)... + if (enablePrint) { + // ...print the number to Serial + Serial.print("Number is: "); + Serial.println(number); + } else { + Serial.println("Printing is disabled, sorry:("); + } +} + +void applyInvert() { + menu.invertKeysDuringEdit(invert); + // Print invert variable to Serial + Serial.print("Invert: "); + Serial.println(invert); +} \ No newline at end of file diff --git a/examples/AdafruitGFX/Example-06_Todo-List/Example-06_Todo-List.ino b/examples/AdafruitGFX/Example-06_Todo-List/Example-06_Todo-List.ino new file mode 100644 index 0000000..d902f04 --- /dev/null +++ b/examples/AdafruitGFX/Example-06_Todo-List/Example-06_Todo-List.ino @@ -0,0 +1,517 @@ +/* + Using GEM to create Todo list utilizing (god-forbidden) dynamic memory allocation (`new` and `delete`). + Using rotary encoder as an input source. Todo items can be dynamically added to the list, + marked completed and cleared (removed from the list). Additional settings are provided + (e.g. changing menu pointer style and order of characters in edit mode). + + Note, that generally it is not recommended to implement dynamic memory allocation in microcontroller-based + projects for a number of reasons (mostly due to memory limitations and lack of supervisory OS to handle + memory management). Consider this example as an experiment and merely demonstration of some of the GEM + features, rather than a guide on how to manage dynamic memory in your project. + + Adafruit GFX library is used to draw menu. + KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. + + Points of improvement to consider: + - Prevent adding new Todo items if insufficient amount of RAM is available + - Add button to Uncheck all and/or Check all Todo items + - Use external storage (e.g. SD Card) to store data + - Make portable by adding battery + + This example uses the same schematics/breadboard as Example-05_Encoder (supplied with GEM). + + Additional info (including the breadboard view) available on GitHub: + https://github.com/Spirik/GEM + + This example code is in the public domain. +*/ + +#include +#include + +// SPI and I2C libraries required by SH1106 display +//#include +#include +#include + +// Hardware-specific library for SH1106. +// Include library that matches your setup (see https://learn.adafruit.com/adafruit-gfx-graphics-library for details) +#include + +//====================== CLASSES + +// Class representing Todo item +class TodoItem { + public: + /* + @param 'title_' - title of Todo item + */ + TodoItem(char* title_){ + strcpy(title, title_); + menuItem = new GEMItem(title, completed); + }; + + char title[GEM_STR_LEN]; // Title of Todo item + bool completed = false; // Checkbox status + GEMItem* menuItem = nullptr; // Pointer to corresponding menu item +}; + +//====================== MISC + +// Custom splash (disabled, because it won't be visible on the screen of buffer-equiped displays +// such as this one with Adafruit GFX library) +/* #define splashWidth 27 +#define splashHeight 8 +static const unsigned char splashBits [] PROGMEM = { + 0xf8, 0xe3, 0xc3, 0x80, 0x01, 0x10, 0x24, 0x40, 0x21, 0x12, 0x24, 0x40, 0x21, 0x12, 0x24, 0x40, + 0x21, 0x12, 0x24, 0x40, 0x21, 0x12, 0x24, 0x40, 0x20, 0xe3, 0xc3, 0x80, 0x00, 0x00, 0x00, 0x00 +}; */ + +//====================== WORKING WITH ENCODER + +// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button) +#define KEY_A 1 +#define KEY_B 2 +#define KEY_C 3 + +// Pins encoder is connected to +const byte channelA = 2; +const byte channelB = 3; +const byte buttonPin = 4; + +byte chanB = HIGH; // Variable to store Channel B readings + +// Array of Key objects that will link GEM key identifiers with dedicated pins +// (it is only necessary to detect signal change on a single channel of the encoder, either A or B; +// order of the channel and push-button Key objects in an array is not important) +Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}}; + +// Create KeyDetector object +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); +// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay +// as the third argument to KeyDetector constructor. +// Make sure to adjust debounce delay to better fit your rotary encoder. +// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case). +// Analog threshold is not necessary for this example and is set to default value 16. +KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10, /* analogThreshold= */ 16, /* pullup= */ true); + +bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers +bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times +const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms +long keyPressTime = 0; // Variable to hold time of the key press event +long now; // Variable to hold current time taken with millis() function at the beginning of loop() + +//====================== OBTAINING RAM STATUS + +// Variable to store free RAM. It is an int, so overflows and rollover may occur, in that case, free RAM won't be displayed +int freeRam; +GEMItem menuItemRam("Free RAM:", freeRam, GEM_READONLY); // Menu item associated with it + +// Free RAM calculations +// (based on https://docs.arduino.cc/learn/programming/memory-guide and https://github.com/mpflaga/Arduino-MemoryFree/) +#if defined(__arm__) && !defined(ARDUINO_ARCH_RP2040) +// ARM (except RP2040, which won't display correct values, probably due to internal implementation) + +extern "C" char* sbrk(int incr); + +void calculateFreeRam() { + freeRam = getFreeRam(); +} + +int getFreeRam() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} + +#elif defined(ARDUINO_ARCH_AVR) +// ARM + +void calculateFreeRam() { + freeRam = getFreeRam(); +} + +int getFreeRam() { + extern int __heap_start,*__brkval; + int v; + return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int) __brkval); +} + +#elif defined(ARDUINO_ARCH_ESP32) +// ESP32 + +void calculateFreeRam() { + freeRam = ESP.getFreeHeap(); +} + +#else +// Correct detection of free RAM not implemented + +void calculateFreeRam() { + freeRam = -1; +} + +#endif + +//====================== DISPLAY + +/* Uncomment to initialize the I2C address, uncomment only one, if you get a totally blank screen try the other */ +#define i2c_Address 0x3c // Initialize with the I2C addr 0x3C Typically eBay OLED's +//#define i2c_Address 0x3d // Initialize with the I2C addr 0x3D Typically Adafruit OLED's + +// Macro constants (aliases) for display setup +#define SCREEN_WIDTH 128 // OLED display width, in pixels +#define SCREEN_HEIGHT 64 // OLED display height, in pixels +#define OLED_RESET -1 // QT-PY / XIAO + +// Create an instance of the Adafruit GFX library. +// Use constructor that matches your setup (see https://learn.adafruit.com/adafruit-gfx-graphics-library for details). +// SH1106 based display is used in the example. +// This instance is used to call all the subsequent Adafruit GFX functions (internally from GEM library, +// or manually in your sketch if it is required) +Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); + +//====================== MENU AND CORRESPONDING ELEMENTS + +// Create variable that will be editable through option select and create associated option select. +// This variable will be passed to menu.invertKeysDuringEdit(), and naturally can be presented as a boolean, +// but is declared as a byte type to be used in an option select rather than checkbox (for demonstration purposes) +byte invert = 1; +SelectOptionByte selectInvertOptions[] = {{"Invert", 1}, {"Normal", 0}}; +GEMSelect selectInvert(sizeof(selectInvertOptions)/sizeof(SelectOptionByte), selectInvertOptions); + +// Create menu item for option select with applyInvert() callback function +void applyInvert(); // Forward declaration +GEMItem menuItemInvert("Chars order:", invert, selectInvert, applyInvert); + +// Create variable holding appearance of menu pointer that will be editable through option select and create associated option select. +byte menuPointer = GEM_POINTER_ROW; +SelectOptionByte selectMenuPointerOptions[] = {{"Row", GEM_POINTER_ROW}, {"Dash", GEM_POINTER_DASH}}; +GEMSelect selectMenuPointer(sizeof(selectMenuPointerOptions)/sizeof(SelectOptionByte), selectMenuPointerOptions); + +// Create menu item for option select with applyMenuPointer() callback function +void applyMenuPointer(); // Forward declaration +GEMItem menuItemMenuPointer("Menu pointer:", menuPointer, selectMenuPointer, applyMenuPointer); + +// Create variable that will temporarily hold title of new Todo list item +char newItemTitle[GEM_STR_LEN]; + +// Create menu item for title of new Todo list item +void editTitle(); // Forward declaration +GEMItem menuItemTitle("Title:", newItemTitle, editTitle); + +// Create menu button that will trigger addItem() function. It will add new record to Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void addItem(); // Forward declaration +GEMItem menuItemButtonAdd("Add", addItem); + +// Create menu button that will trigger clearItems() function. It will remove completed items from Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void clearCompleted(); // Forward declaration +GEMItem menuItemButtonClear("Clear completed", clearCompleted); + +// Create menu button that will trigger clearAll() function. It will remove all items from Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void clearAll(); // Forward declaration +GEMItem menuItemButtonClearAll("Clear all", clearAll); + +// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. +// Menu can have multiple menu pages (linked to each other) with multiple menu items each +GEMPage menuPageMain("Main Menu"); // Main page +GEMPage menuPageList("Todo", menuPageMain); // Todo list submenu +GEMPage menuPageAdd("Add Item", menuPageList); // Add item submenu +GEMPage menuPageManage("Manage", menuPageMain); // Manage submenu +GEMPage menuPageSettings("Settings", menuPageMain); // Settings submenu + +// Create menu item links to submenu pages +GEMItem menuItemLinkList("List", menuPageList); // Create menu item linked to List menu page +GEMItem menuItemLinkAdd("Add+", menuPageAdd); // Create menu item linked to Add menu page +GEMItem menuItemLinkManage("Manage", menuPageManage); // Create menu item linked to Manage menu page +GEMItem menuItemLinkSettings("Settings", menuPageSettings); // Create menu item linked to Settings menu page + +// Create GEMAppearance objects +GEMAppearance appearanceGeneral = { /* menuPointerType= */ menuPointer, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86}; +GEMAppearance appearanceList = appearanceGeneral; +GEMAppearance appearanceAdd = appearanceGeneral; + +// Create menu object of class GEM_adafruit_gfx. Supply its constructor with reference to display object we created earlier +GEM_adafruit_gfx menu(display, appearanceGeneral); + +void setup() { + // Pin modes + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + pinMode(buttonPin, INPUT_PULLUP); + + // Serial communication setup + Serial.begin(115200); + + // Show image buffer on the display hardware. + // Since the buffer is intialized with an Adafruit splashscreen + // internally, this will display the splashscreen. + delay(250); // Wait for the OLED to power up + display.begin(i2c_Address, true); // Address 0x3C default + + display.display(); + delay(2000); + + // Clear the buffer + display.clearDisplay(); + + // Explicitly set correct colors for monochrome OLED screen + menu + .setForegroundColor(SH110X_WHITE) + .setBackgroundColor(SH110X_BLACK); + + menu + // Turn inverted order of characters during edit mode on (feels more natural when using encoder) + .invertKeysDuringEdit(invert) + // Disable GEM splash (it won't be visible on the screen of buffer-equiped displays such as this one any way) + .setSplashDelay(0) + // Menu init, setup and draw + .init(); + setupMenu(); + + calculateFreeRam(); + if (freeRam < 0) { + // Hide RAM counter if not available or rolled over an int value + menuItemRam.hide(); + } + + menu.drawMenu(); + + display.display(); + + Serial.println(F("Initialized")); +} + +void setupMenu() { + // Add menu items to menu page + menuPageMain + .addMenuItem(menuItemLinkList) + .addMenuItem(menuItemLinkManage) + .addMenuItem(menuItemLinkSettings); + + appearanceList.menuValuesLeftOffset = 118; + + // Add menu items to List menu page + menuPageList + .setAppearance(&appearanceList) + .addMenuItem(menuItemLinkAdd); + + // Turn on adjusted order of ASCII characters when editing title + menuItemTitle.setAdjustedASCIIOrder(); + + appearanceAdd.menuValuesLeftOffset = 46; + + // Add menu items to Add menu page + menuPageAdd + .setAppearance(&appearanceAdd) + .addMenuItem(menuItemTitle) + .addMenuItem(menuItemButtonAdd); + + // Add menu items to Manage menu page + menuPageManage + .addMenuItem(menuItemRam) + .addMenuItem(menuItemButtonClear) + .addMenuItem(menuItemButtonClearAll); + + // Add menu items to Settings menu page + menuPageSettings + .addMenuItem(menuItemInvert) + .addMenuItem(menuItemMenuPointer); + + // Set List page as a starting one + menu.setMenuPageCurrent(menuPageList); + + // Hide Add button by default (until Todo item title is entered) + menuItemButtonAdd.hide(); +} + +// loop() is primarily used to manage rotary encoder operation, +// with six push-buttons instead it is much shorter +void loop() { + // Get current time to use later on + now = millis(); + + // If menu is ready to accept button press... + if (menu.readyForKey()) { + chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm + // ...detect key press using KeyDetector library + // and pass pressed button to menu + myKeyDetector.detect(); + + // Calculate RAM each loop iteration + calculateFreeRam(); + + switch (myKeyDetector.trigger) { + case KEY_A: + // Signal from Channel A of encoder was detected + if (chanB == LOW) { + // If channel B is low then the knob was rotated CCW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... + menu.registerKeyPress(GEM_KEY_LEFT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_UP otherwise + menu.registerKeyPress(GEM_KEY_UP); + } + } else { + // If channel B is high then the knob was rotated CW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... + menu.registerKeyPress(GEM_KEY_RIGHT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_DOWN otherwise + menu.registerKeyPress(GEM_KEY_DOWN); + } + } + break; + case KEY_C: + // Button was pressed + // Save current time as a time of the key press event + keyPressTime = now; + break; + } + switch (myKeyDetector.triggerRelease) { + case KEY_C: + // Button was released + if (!secondaryPressed) { + // If button was not used as a modifier to rotation action... + if (now <= keyPressTime + keyPressDelay) { + // ...and if not enough time passed since keyPressTime, + // treat key that was pressed as Ok button + menu.registerKeyPress(GEM_KEY_OK); + } + } + secondaryPressed = false; + cancelPressed = false; + break; + } + // After keyPressDelay passed since keyPressTime + if (now > keyPressTime + keyPressDelay) { + switch (myKeyDetector.current) { + case KEY_C: + if (!secondaryPressed && !cancelPressed) { + // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet + // Treat key that was pressed as Cancel button + menu.registerKeyPress(GEM_KEY_CANCEL); + cancelPressed = true; + } + break; + } + } + + // Necessary to actually draw current state of the menu on screen of buffer-equiped display with Adafruit GFX library + display.display(); + } +} + +void flashButtonTitle(const char* title, bool redraw = true) { + GEMItem* menuItemButtonTmp = menu.getCurrentMenuPage()->getCurrentMenuItem(); + const char* titleOrig = menuItemButtonTmp->getTitle(); + menuItemButtonTmp->setTitle(title); + menu.drawMenu(); + display.display(); + delay(1000); + menuItemButtonTmp->setTitle(titleOrig); + if (redraw) { + menu.drawMenu(); + display.display(); + } +} + +void applyInvert() { + menu.invertKeysDuringEdit(invert); + + // Print invert variable to Serial + Serial.print(F("Invert: ")); + Serial.println(invert); +} + +void applyMenuPointer() { + appearanceGeneral.menuPointerType = menuPointer; + menu.setAppearance(appearanceGeneral); // Need to call setAppearance() when changing general appearance + appearanceList.menuPointerType = menuPointer; // No need to call setAppearance() when changing apperance of menu pages, because it submitted as a pointer + appearanceAdd.menuPointerType = menuPointer; + + // Print invert variable to Serial + Serial.print(F("Menu pointer: ")); + Serial.println(menuPointer); +} + +void editTitle() { + menuItemButtonAdd.hide(newItemTitle[0] == '\0'); +} + +void addItem() { + if (newItemTitle[0] != '\0') { + Serial.print(F("Add Item: ")); + Serial.println(newItemTitle); + + // Creating new TodoItem object and adding corresponding menu item to menu page + menuItemLinkAdd.hide(); // Temporarily hide Add button to add new item at the end of the list (but before hidden button) + TodoItem* tempItem = new TodoItem(newItemTitle); + tempItem->menuItem->setCallbackVal(tempItem); // Save pointer to Todo item in a GEMCallbackData struct inside corresponding menu item + menuPageList.addMenuItem(*tempItem->menuItem, GEM_LAST_POS, GEM_ITEMS_VISIBLE); + menuItemLinkAdd.show(); + memset(newItemTitle, '\0', GEM_STR_LEN - 1); + + // Temporarily change title of Add button, but w/o redrawing menu (because we will hide it) + flashButtonTitle("Item added!", false); + + menuItemButtonAdd.hide(); + } + calculateFreeRam(); + menu.drawMenu(); + display.display(); +} + +void clearItems(bool onlyCompleted = true) { + GEMItem* menuItemTmp = menuPageList.getMenuItem(1); // Get first Todo item in a list to start traversing through menu items + Serial.println(F("Clearing items:")); + while (menuItemTmp->getLinkedVariablePointer() != nullptr) { + GEMItem* nextItem = menuItemTmp->getMenuItemNext(); // Save pointer to a next item + bool completed = *(bool*)menuItemTmp->getLinkedVariablePointer(); // Save completed status + if (completed || !onlyCompleted) { + // If linked boolean variable is true, then consider Todo item completed and ready to be removed + // (and remove it anyway in case if onlyCompleted set to false) + Serial.print(completed ? "[x]" : "[ ]"); + Serial.println(menuItemTmp->getTitle()); + TodoItem* todoItemTmp = (TodoItem*)menuItemTmp->getCallbackData().valPointer; // Get pointer to corresponsing TodoItem object + menuItemTmp->remove(); // Remove menu item from menu page + delete menuItemTmp; // Delete GEMItem object + delete todoItemTmp; // Delete TodoItem object + /* + Note 1: sometimes (e.g. on ARM-based MCUs, but not on AVR or ESP32) deleting completed Todo items + doesn't immediately reflect on the amount of free RAM (as reported by getFreeRam()), however + if new Todo item is created afterwards (after deleting completed one) free RAM counter won't change. + That shows that deleteing objects is actually works (just not always reflected on the visible amount of free RAM, + probably for reasons discussed here: https://forum.arduino.cc/t/memory-no-getting-cleaned-up-after-delete/894404), + and new item presumably occupies previously freed memory. This may be related to so-called "buried heap space". + Note 2: amount of displayed free RAM may change after moving cursor from button after clearing, for the same amount every + time for some reason, even if no actual Todo items was deleted (it may something to do with processes needed to redraw + menu and/or stack allocation for clearItems() call). However, previous statement (Note 1) still holds. + */ + } + menuItemTmp = nextItem; + } + calculateFreeRam(); + menu.drawMenu(); +} + +void clearCompleted() { + clearItems(); + flashButtonTitle("Cleared!"); +} + +void clearAll() { + clearItems(false); + flashButtonTitle("Cleared!"); +} diff --git a/examples/AltSerialGraphicLCD/Example-01_Basic/Example-01_Basic.ino b/examples/AltSerialGraphicLCD/Example-01_Basic/Example-01_Basic.ino index 10cc809..701dae5 100644 --- a/examples/AltSerialGraphicLCD/Example-01_Basic/Example-01_Basic.ino +++ b/examples/AltSerialGraphicLCD/Example-01_Basic/Example-01_Basic.ino @@ -1,8 +1,8 @@ /* Basic menu example using GEM library. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, - and a button, pressing of which will result in int variable value printed to Serial monitor if boolean variable is set to true. + Simple one page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. AltSerialGraphicLCD library is used to draw menu. KeyDetector library is used to detect push-buttons presses. @@ -31,7 +31,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object const byte rxPin = 8; @@ -44,7 +44,7 @@ GLCD glcd(serialLCD); // Create variables that will be editable through the menu and assign them initial values int number = -512; -boolean enablePrint = false; +bool enablePrint = false; // Create two menu item objects of class GEMItem, linked to number and enablePrint variables GEMItem menuItemInt("Number:", number); @@ -62,6 +62,8 @@ GEMPage menuPageMain("Main Menu"); // Create menu object of class GEM. Supply its constructor with reference to glcd object we created earlier GEM menu(glcd); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM menu(glcd, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes diff --git a/examples/AltSerialGraphicLCD/Example-02_Blink/Example-02_Blink.ino b/examples/AltSerialGraphicLCD/Example-02_Blink/Example-02_Blink.ino index 3311748..9d172cf 100644 --- a/examples/AltSerialGraphicLCD/Example-02_Blink/Example-02_Blink.ino +++ b/examples/AltSerialGraphicLCD/Example-02_Blink/Example-02_Blink.ino @@ -36,7 +36,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object const byte rxPin = 8; @@ -56,7 +56,7 @@ char label[GEM_STR_LEN] = "Blink!"; // Maximum length of the string should not e unsigned long previousMillis = 0; // Variable to hold current LED state -boolean ledOn = false; +bool ledOn = false; // Create two menu item objects of class GEMItem, linked to interval and label variables // with validateInterval() callback function attached to interval menu item, @@ -86,6 +86,8 @@ GEMItem menuItemMainSettings("Settings", menuPageSettings); // Create menu object of class GEM. Supply its constructor with reference to glcd object we created earlier GEM menu(glcd); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM menu(glcd, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes diff --git a/examples/AltSerialGraphicLCD/Example-03_Party-Hard/Example-03_Party-Hard.ino b/examples/AltSerialGraphicLCD/Example-03_Party-Hard/Example-03_Party-Hard.ino index 88b6b7c..d3ff7da 100644 --- a/examples/AltSerialGraphicLCD/Example-03_Party-Hard/Example-03_Party-Hard.ino +++ b/examples/AltSerialGraphicLCD/Example-03_Party-Hard/Example-03_Party-Hard.ino @@ -3,7 +3,7 @@ editable menu items with validation callbacks, setting readonly mode, creation of context with context.allowExit set to false in order to use push-buttons to control scene within context's loop routine. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, one option select, + Simple one page menu with one editable menu item associated with int variable, one with bool variable, one option select, and a button, pressing of which will launch an animation sequence drawn to the screen. Delay between frames is determined by value of int variable, setting of which to 0 will enable manual control of the frames through navigation push-buttons. @@ -39,7 +39,7 @@ Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, dow KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); // To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay // as the third argument to KeyDetector constructor: -// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10); +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10); // Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object const byte rxPin = 8; @@ -52,7 +52,7 @@ GLCD glcd(serialLCD); // Create variables that will be editable through the menu and assign them initial values int interval = 200; -boolean strobe = false; +bool strobe = false; // Create variable that will be editable through option select and create associated option select byte tempo = 0; @@ -90,6 +90,8 @@ GEMPage menuPageMain("Party Hard"); // Create menu object of class GEM. Supply its constructor with reference to glcd object we created earlier GEM menu(glcd); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM menu(glcd, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Push-buttons pin modes @@ -183,7 +185,7 @@ void drawSprite(const uint8_t PROGMEM *_splash, byte _mode) { } // Draw frame based on direction of animation -void drawFrame(boolean forward) { +void drawFrame(bool forward) { if (forward) { // Next frame currentFrame = (currentFrame == framesCount ? 1 : currentFrame+1); diff --git a/examples/AltSerialGraphicLCD/Example-05_Encoder/Example-05_Encoder.ino b/examples/AltSerialGraphicLCD/Example-05_Encoder/Example-05_Encoder.ino new file mode 100644 index 0000000..67bc114 --- /dev/null +++ b/examples/AltSerialGraphicLCD/Example-05_Encoder/Example-05_Encoder.ino @@ -0,0 +1,246 @@ +/* + Basic menu example using GEM library. Using rotary encoder as an input source. + + Simple two page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. + + Second menu page with option select is added to better demonstrate operation of the menu with rotary encoder. + + AltSerialGraphicLCD library is used to draw menu. + KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. + + Additional info (including the breadboard view) available on GitHub: + https://github.com/Spirik/GEM + + This example code is in the public domain. +*/ + +#include +#include + +// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button) +#define KEY_A 1 +#define KEY_B 2 +#define KEY_C 3 + +// Pins encoder is connected to +const byte channelA = 2; +const byte channelB = 3; +const byte buttonPin = 4; + +byte chanB = HIGH; // Variable to store Channel B readings + +// Array of Key objects that will link GEM key identifiers with dedicated pins +// (it is only necessary to detect signal change on a single channel of the encoder, either A or B; +// order of the channel and push-button Key objects in an array is not important) +Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}}; +//Key keys[] = {{KEY_C, buttonPin}, {KEY_A, channelA}}; + +// Create KeyDetector object +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); +// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay +// as the third argument to KeyDetector constructor. +// Make sure to adjust debounce delay to better fit your rotary encoder. +// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case). +// Analog threshold is not necessary for this example and is set to default value 16. +KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 5, /* analogThreshold= */ 16, /* pullup= */ true); + +bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers +bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times +const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms +long keyPressTime = 0; // Variable to hold time of the key press event +long now; // Variable to hold current time taken with millis() function at the beginning of loop() + +// Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object +const byte rxPin = 8; +const byte txPin = 9; +SoftwareSerial serialLCD(rxPin, txPin); + +// Create an instance of the GLCD class. This instance is used to call all the subsequent GLCD functions +// (internally from GEM library, or manually in your sketch if it is required) +GLCD glcd(serialLCD); + +// Create variables that will be editable through the menu and assign them initial values +int number = -512; +bool enablePrint = false; + +// Create variable that will be editable through option select and create associated option select. +// This variable will be passed to menu.invertKeysDuringEdit(), and naturally can be presented as a bool, +// but is declared as a byte type to be used in an option select rather than checkbox (for demonstration purposes) +byte invert = 1; +SelectOptionByte selectInvertOptions[] = {{"Invert", 1}, {"Normal", 0}}; +GEMSelect selectInvert(sizeof(selectInvertOptions)/sizeof(SelectOptionByte), selectInvertOptions); + +// Create menu item for option select with applyInvert() callback function +void applyInvert(); // Forward declaration +GEMItem menuItemInvert("Chars order:", invert, selectInvert, applyInvert); + +// Create two menu item objects of class GEMItem, linked to number and enablePrint variables +GEMItem menuItemInt("Number:", number); +GEMItem menuItemBool("Enable print:", enablePrint); + +// Create menu button that will trigger printData() function. It will print value of our number variable +// to Serial monitor if enablePrint is true. We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void printData(); // Forward declaration +GEMItem menuItemButton("Print", printData); + +// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. +// Menu can have multiple menu pages (linked to each other) with multiple menu items each +GEMPage menuPageMain("Main Menu"); // Main page +GEMPage menuPageSettings("Settings"); // Settings submenu + +// Create menu item linked to Settings menu page +GEMItem menuItemMainSettings("Settings", menuPageSettings); + +// Create menu object of class GEM. Supply its constructor with reference to glcd object we created earlier +GEM menu(glcd); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM menu(glcd, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ 5, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); + +void setup() { + // Pin modes + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + pinMode(buttonPin, INPUT_PULLUP); + + // Serial communication setup + Serial.begin(115200); + serialLCD.begin(115200); + + // LCD reset + delay(500); + glcd.reset(); + delay(1000); + // Uncomment the following lines in dire situations + // (e.g. when screen becomes unresponsive after shutdown) + glcd.reset(); + delay(1000); + + // Turn inverted order of characters during edit mode on (feels more natural when using encoder) + menu.invertKeysDuringEdit(invert); + + // Menu init, setup and draw + menu.init(); + setupMenu(); + menu.drawMenu(); + + Serial.println("Initialized"); +} + +void setupMenu() { + // Add menu items to Settings menu page + menuPageSettings.addMenuItem(menuItemInvert); + + // Add menu items to menu page + menuPageMain.addMenuItem(menuItemMainSettings); + menuPageMain.addMenuItem(menuItemInt); + menuPageMain.addMenuItem(menuItemBool); + menuPageMain.addMenuItem(menuItemButton); + + // Specify parent menu page for the Settings menu page + menuPageSettings.setParentMenuPage(menuPageMain); + + // Add menu page to menu and set it as current + menu.setMenuPageCurrent(menuPageMain); +} + +void loop() { + // Get current time to use later on + now = millis(); + + // If menu is ready to accept button press... + if (menu.readyForKey()) { + chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm + // ...detect key press using KeyDetector library + // and pass pressed button to menu + myKeyDetector.detect(); + + switch (myKeyDetector.trigger) { + case KEY_A: + // Signal from Channel A of encoder was detected + if (chanB == LOW) { + // If channel B is low then the knob was rotated CCW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... + Serial.println("Rotation CCW with button pressed"); + menu.registerKeyPress(GEM_KEY_LEFT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_UP otherwise + Serial.println("Rotation CCW"); + menu.registerKeyPress(GEM_KEY_UP); + } + } else { + // If channel B is high then the knob was rotated CW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... + Serial.println("Rotation CW with button pressed"); + menu.registerKeyPress(GEM_KEY_RIGHT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_DOWN otherwise + Serial.println("Rotation CW"); + menu.registerKeyPress(GEM_KEY_DOWN); + } + } + break; + case KEY_C: + // Button was pressed + Serial.println("Button pressed"); + // Save current time as a time of the key press event + keyPressTime = now; + break; + } + switch (myKeyDetector.triggerRelease) { + case KEY_C: + // Button was released + Serial.println("Button released"); + if (!secondaryPressed) { + // If button was not used as a modifier to rotation action... + if (now <= keyPressTime + keyPressDelay) { + // ...and if not enough time passed since keyPressTime, + // treat key that was pressed as Ok button + menu.registerKeyPress(GEM_KEY_OK); + } + } + secondaryPressed = false; + cancelPressed = false; + break; + } + // After keyPressDelay passed since keyPressTime + if (now > keyPressTime + keyPressDelay) { + switch (myKeyDetector.current) { + case KEY_C: + if (!secondaryPressed && !cancelPressed) { + // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet + Serial.println("Button remained pressed"); + // Treat key that was pressed as Cancel button + menu.registerKeyPress(GEM_KEY_CANCEL); + cancelPressed = true; + } + break; + } + } + } +} + +void printData() { + // If enablePrint flag is set to true (checkbox on screen is checked)... + if (enablePrint) { + // ...print the number to Serial + Serial.print("Number is: "); + Serial.println(number); + } else { + Serial.println("Printing is disabled, sorry:("); + } +} + +void applyInvert() { + menu.invertKeysDuringEdit(invert); + // Print invert variable to Serial + Serial.print("Invert: "); + Serial.println(invert); +} \ No newline at end of file diff --git a/examples/AltSerialGraphicLCD/Example-06_Todo-List/Example-06_Todo-List.ino b/examples/AltSerialGraphicLCD/Example-06_Todo-List/Example-06_Todo-List.ino new file mode 100644 index 0000000..9e07f03 --- /dev/null +++ b/examples/AltSerialGraphicLCD/Example-06_Todo-List/Example-06_Todo-List.ino @@ -0,0 +1,484 @@ +/* + Using GEM to create Todo list utilizing (god-forbidden) dynamic memory allocation (`new` and `delete`). + Using rotary encoder as an input source. Todo items can be dynamically added to the list, + marked completed and cleared (removed from the list). Additional settings are provided + (e.g. changing menu pointer style and order of characters in edit mode). + + Note, that generally it is not recommended to implement dynamic memory allocation in microcontroller-based + projects for a number of reasons (mostly due to memory limitations and lack of supervisory OS to handle + memory management). Consider this example as an experiment and merely demonstration of some of the GEM + features, rather than a guide on how to manage dynamic memory in your project. + + AltSerialGraphicLCD library is used to draw menu. + KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. + + Points of improvement to consider: + - Prevent adding new Todo items if insufficient amount of RAM is available + - Add button to Uncheck all and/or Check all Todo items + - Use external storage (e.g. SD Card) to store data + - Make portable by adding battery + + This example uses the same schematics/breadboard as Example-05_Encoder (supplied with GEM). + + Additional info (including the breadboard view) available on GitHub: + https://github.com/Spirik/GEM + + This example code is in the public domain. +*/ + +#include +#include + +//====================== CLASSES + +// Class representing Todo item +class TodoItem { + public: + /* + @param 'title_' - title of Todo item + */ + TodoItem(char* title_){ + strcpy(title, title_); + menuItem = new GEMItem(title, completed); + }; + + char title[GEM_STR_LEN]; // Title of Todo item + bool completed = false; // Checkbox status + GEMItem* menuItem = nullptr; // Pointer to corresponding menu item +}; + +//====================== MISC + +// Custom splash +static const uint8_t splashBits [] PROGMEM = { + 27, 8, + 0x01, 0x01, 0x7d, 0x01, 0x01, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00, 0x00, 0x7d, 0x41, + 0x41, 0x41, 0x3e, 0x00, 0x00, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00 +}; + +//====================== WORKING WITH ENCODER + +// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button) +#define KEY_A 1 +#define KEY_B 2 +#define KEY_C 3 + +// Pins encoder is connected to +const byte channelA = 2; +const byte channelB = 3; +const byte buttonPin = 4; + +byte chanB = HIGH; // Variable to store Channel B readings + +// Array of Key objects that will link GEM key identifiers with dedicated pins +// (it is only necessary to detect signal change on a single channel of the encoder, either A or B; +// order of the channel and push-button Key objects in an array is not important) +Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}}; + +// Create KeyDetector object +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); +// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay +// as the third argument to KeyDetector constructor. +// Make sure to adjust debounce delay to better fit your rotary encoder. +// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case). +// Analog threshold is not necessary for this example and is set to default value 16. +KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10, /* analogThreshold= */ 16, /* pullup= */ true); + +bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers +bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times +const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms +long keyPressTime = 0; // Variable to hold time of the key press event +long now; // Variable to hold current time taken with millis() function at the beginning of loop() + +//====================== OBTAINING RAM STATUS + +// Variable to store free RAM. It is an int, so overflows and rollover may occur, in that case, free RAM won't be displayed +int freeRam; +GEMItem menuItemRam("Free RAM:", freeRam, GEM_READONLY); // Menu item associated with it + +// Free RAM calculations +// (based on https://docs.arduino.cc/learn/programming/memory-guide and https://github.com/mpflaga/Arduino-MemoryFree/) +#if defined(__arm__) && !defined(ARDUINO_ARCH_RP2040) +// ARM (except RP2040, which won't display correct values, probably due to internal implementation) + +extern "C" char* sbrk(int incr); + +void calculateFreeRam() { + freeRam = getFreeRam(); +} + +int getFreeRam() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} + +#elif defined(ARDUINO_ARCH_AVR) +// ARM + +void calculateFreeRam() { + freeRam = getFreeRam(); +} + +int getFreeRam() { + extern int __heap_start,*__brkval; + int v; + return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int) __brkval); +} + +#elif defined(ARDUINO_ARCH_ESP32) +// ESP32 + +void calculateFreeRam() { + freeRam = ESP.getFreeHeap(); +} + +#else +// Correct detection of free RAM not implemented + +void calculateFreeRam() { + freeRam = -1; +} + +#endif + +//====================== DISPLAY + +// Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object +const byte rxPin = 8; +const byte txPin = 9; +SoftwareSerial serialLCD(rxPin, txPin); + +// Create an instance of the GLCD class. This instance is used to call all the subsequent GLCD functions +// (internally from GEM library, or manually in your sketch if it is required) +GLCD glcd(serialLCD); + +//====================== MENU AND CORRESPONDING ELEMENTS + +// Create variable that will be editable through option select and create associated option select. +// This variable will be passed to menu.invertKeysDuringEdit(), and naturally can be presented as a boolean, +// but is declared as a byte type to be used in an option select rather than checkbox (for demonstration purposes) +byte invert = 1; +SelectOptionByte selectInvertOptions[] = {{"Invert", 1}, {"Normal", 0}}; +GEMSelect selectInvert(sizeof(selectInvertOptions)/sizeof(SelectOptionByte), selectInvertOptions); + +// Create menu item for option select with applyInvert() callback function +void applyInvert(); // Forward declaration +GEMItem menuItemInvert("Chars order:", invert, selectInvert, applyInvert); + +// Create variable holding appearance of menu pointer that will be editable through option select and create associated option select. +byte menuPointer = GEM_POINTER_ROW; +SelectOptionByte selectMenuPointerOptions[] = {{"Row", GEM_POINTER_ROW}, {"Dash", GEM_POINTER_DASH}}; +GEMSelect selectMenuPointer(sizeof(selectMenuPointerOptions)/sizeof(SelectOptionByte), selectMenuPointerOptions); + +// Create menu item for option select with applyMenuPointer() callback function +void applyMenuPointer(); // Forward declaration +GEMItem menuItemMenuPointer("Menu pointer:", menuPointer, selectMenuPointer, applyMenuPointer); + +// Create variable that will temporarily hold title of new Todo list item +char newItemTitle[GEM_STR_LEN]; + +// Create menu item for title of new Todo list item +void editTitle(); // Forward declaration +GEMItem menuItemTitle("Title:", newItemTitle, editTitle); + +// Create menu button that will trigger addItem() function. It will add new record to Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void addItem(); // Forward declaration +GEMItem menuItemButtonAdd("Add", addItem); + +// Create menu button that will trigger clearItems() function. It will remove completed items from Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void clearCompleted(); // Forward declaration +GEMItem menuItemButtonClear("Clear completed", clearCompleted); + +// Create menu button that will trigger clearAll() function. It will remove all items from Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void clearAll(); // Forward declaration +GEMItem menuItemButtonClearAll("Clear all", clearAll); + +// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. +// Menu can have multiple menu pages (linked to each other) with multiple menu items each +GEMPage menuPageMain("Main Menu"); // Main page +GEMPage menuPageList("Todo", menuPageMain); // Todo list submenu +GEMPage menuPageAdd("Add Item", menuPageList); // Add item submenu +GEMPage menuPageManage("Manage", menuPageMain); // Manage submenu +GEMPage menuPageSettings("Settings", menuPageMain); // Settings submenu + +// Create menu item links to submenu pages +GEMItem menuItemLinkList("List", menuPageList); // Create menu item linked to List menu page +GEMItem menuItemLinkAdd("Add+", menuPageAdd); // Create menu item linked to Add menu page +GEMItem menuItemLinkManage("Manage", menuPageManage); // Create menu item linked to Manage menu page +GEMItem menuItemLinkSettings("Settings", menuPageSettings); // Create menu item linked to Settings menu page + +// Create GEMAppearance objects +GEMAppearance appearanceGeneral = { /* menuPointerType= */ menuPointer, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86}; +GEMAppearance appearanceList = appearanceGeneral; +GEMAppearance appearanceAdd = appearanceGeneral; + +// Create menu object of class GEM. Supply its constructor with reference to glcd object we created earlier +GEM menu(glcd, appearanceGeneral); + +void setup() { + // Pin modes + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + pinMode(buttonPin, INPUT_PULLUP); + + // Serial communication setup + Serial.begin(115200); + serialLCD.begin(115200); + + // LCD reset + delay(500); + glcd.reset(); + delay(1000); + // Uncomment the following lines in dire situations + // (e.g. when screen becomes unresponsive after shutdown) + glcd.reset(); + delay(1000); + + menu + // Turn inverted order of characters during edit mode on (feels more natural when using encoder) + .invertKeysDuringEdit(invert) + // Set custom splash + .setSplash(splashBits) + // Menu init, setup and draw + .init(); + setupMenu(); + + calculateFreeRam(); + if (freeRam < 0) { + // Hide RAM counter if not available or rolled over an int value + menuItemRam.hide(); + } + + menu.drawMenu(); + + Serial.println(F("Initialized")); +} + +void setupMenu() { + // Add menu items to menu page + menuPageMain + .addMenuItem(menuItemLinkList) + .addMenuItem(menuItemLinkManage) + .addMenuItem(menuItemLinkSettings); + + appearanceList.menuValuesLeftOffset = 118; + + // Add menu items to List menu page + menuPageList + .setAppearance(&appearanceList) + .addMenuItem(menuItemLinkAdd); + + // Turn on adjusted order of ASCII characters when editing title + menuItemTitle.setAdjustedASCIIOrder(); + + appearanceAdd.menuValuesLeftOffset = 46; + + // Add menu items to Add menu page + menuPageAdd + .setAppearance(&appearanceAdd) + .addMenuItem(menuItemTitle) + .addMenuItem(menuItemButtonAdd); + + // Add menu items to Manage menu page + menuPageManage + .addMenuItem(menuItemRam) + .addMenuItem(menuItemButtonClear) + .addMenuItem(menuItemButtonClearAll); + + // Add menu items to Settings menu page + menuPageSettings + .addMenuItem(menuItemInvert) + .addMenuItem(menuItemMenuPointer); + + // Set List page as a starting one + menu.setMenuPageCurrent(menuPageList); + + // Hide Add button by default (until Todo item title is entered) + menuItemButtonAdd.hide(); +} + +// loop() is primarily used to manage rotary encoder operation, +// with six push-buttons instead it is much shorter +void loop() { + // Get current time to use later on + now = millis(); + + // If menu is ready to accept button press... + if (menu.readyForKey()) { + chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm + // ...detect key press using KeyDetector library + // and pass pressed button to menu + myKeyDetector.detect(); + + // Calculate RAM each loop iteration + calculateFreeRam(); + + switch (myKeyDetector.trigger) { + case KEY_A: + // Signal from Channel A of encoder was detected + if (chanB == LOW) { + // If channel B is low then the knob was rotated CCW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... + menu.registerKeyPress(GEM_KEY_LEFT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_UP otherwise + menu.registerKeyPress(GEM_KEY_UP); + } + } else { + // If channel B is high then the knob was rotated CW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... + menu.registerKeyPress(GEM_KEY_RIGHT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_DOWN otherwise + menu.registerKeyPress(GEM_KEY_DOWN); + } + } + break; + case KEY_C: + // Button was pressed + // Save current time as a time of the key press event + keyPressTime = now; + break; + } + switch (myKeyDetector.triggerRelease) { + case KEY_C: + // Button was released + if (!secondaryPressed) { + // If button was not used as a modifier to rotation action... + if (now <= keyPressTime + keyPressDelay) { + // ...and if not enough time passed since keyPressTime, + // treat key that was pressed as Ok button + menu.registerKeyPress(GEM_KEY_OK); + } + } + secondaryPressed = false; + cancelPressed = false; + break; + } + // After keyPressDelay passed since keyPressTime + if (now > keyPressTime + keyPressDelay) { + switch (myKeyDetector.current) { + case KEY_C: + if (!secondaryPressed && !cancelPressed) { + // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet + // Treat key that was pressed as Cancel button + menu.registerKeyPress(GEM_KEY_CANCEL); + cancelPressed = true; + } + break; + } + } + } +} + +void flashButtonTitle(const char* title, bool redraw = true) { + GEMItem* menuItemButtonTmp = menu.getCurrentMenuPage()->getCurrentMenuItem(); + const char* titleOrig = menuItemButtonTmp->getTitle(); + menuItemButtonTmp->setTitle(title); + menu.drawMenu(); + delay(1000); + menuItemButtonTmp->setTitle(titleOrig); + if (redraw) { + menu.drawMenu(); + } +} + +void applyInvert() { + menu.invertKeysDuringEdit(invert); + + // Print invert variable to Serial + Serial.print(F("Invert: ")); + Serial.println(invert); +} + +void applyMenuPointer() { + appearanceGeneral.menuPointerType = menuPointer; + menu.setAppearance(appearanceGeneral); // Need to call setAppearance() when changing general appearance + appearanceList.menuPointerType = menuPointer; // No need to call setAppearance() when changing apperance of menu pages, because it submitted as a pointer + appearanceAdd.menuPointerType = menuPointer; + + // Print invert variable to Serial + Serial.print(F("Menu pointer: ")); + Serial.println(menuPointer); +} + +void editTitle() { + menuItemButtonAdd.hide(newItemTitle[0] == '\0'); +} + +void addItem() { + if (newItemTitle[0] != '\0') { + Serial.print(F("Add Item: ")); + Serial.println(newItemTitle); + + // Creating new TodoItem object and adding corresponding menu item to menu page + menuItemLinkAdd.hide(); // Temporarily hide Add button to add new item at the end of the list (but before hidden button) + TodoItem* tempItem = new TodoItem(newItemTitle); + tempItem->menuItem->setCallbackVal(tempItem); // Save pointer to Todo item in a GEMCallbackData struct inside corresponding menu item + menuPageList.addMenuItem(*tempItem->menuItem, GEM_LAST_POS, GEM_ITEMS_VISIBLE); + menuItemLinkAdd.show(); + memset(newItemTitle, '\0', GEM_STR_LEN - 1); + + // Temporarily change title of Add button, but w/o redrawing menu (because we will hide it) + flashButtonTitle("Item added!", false); + + menuItemButtonAdd.hide(); + } + calculateFreeRam(); + menu.drawMenu(); +} + +void clearItems(bool onlyCompleted = true) { + GEMItem* menuItemTmp = menuPageList.getMenuItem(1); // Get first Todo item in a list to start traversing through menu items + Serial.println(F("Clearing items:")); + while (menuItemTmp->getLinkedVariablePointer() != nullptr) { + GEMItem* nextItem = menuItemTmp->getMenuItemNext(); // Save pointer to a next item + bool completed = *(bool*)menuItemTmp->getLinkedVariablePointer(); // Save completed status + if (completed || !onlyCompleted) { + // If linked boolean variable is true, then consider Todo item completed and ready to be removed + // (and remove it anyway in case if onlyCompleted set to false) + Serial.print(completed ? "[x]" : "[ ]"); + Serial.println(menuItemTmp->getTitle()); + TodoItem* todoItemTmp = (TodoItem*)menuItemTmp->getCallbackData().valPointer; // Get pointer to corresponsing TodoItem object + menuItemTmp->remove(); // Remove menu item from menu page + delete menuItemTmp; // Delete GEMItem object + delete todoItemTmp; // Delete TodoItem object + /* + Note 1: sometimes (e.g. on ARM-based MCUs, but not on AVR or ESP32) deleting completed Todo items + doesn't immediately reflect on the amount of free RAM (as reported by getFreeRam()), however + if new Todo item is created afterwards (after deleting completed one) free RAM counter won't change. + That shows that deleteing objects is actually works (just not always reflected on the visible amount of free RAM, + probably for reasons discussed here: https://forum.arduino.cc/t/memory-no-getting-cleaned-up-after-delete/894404), + and new item presumably occupies previously freed memory. This may be related to so-called "buried heap space". + Note 2: amount of displayed free RAM may change after moving cursor from button after clearing, for the same amount every + time for some reason, even if no actual Todo items was deleted (it may something to do with processes needed to redraw + menu and/or stack allocation for clearItems() call). However, previous statement (Note 1) still holds. + */ + } + menuItemTmp = nextItem; + } + calculateFreeRam(); + menu.drawMenu(); +} + +void clearCompleted() { + clearItems(); + flashButtonTitle("Cleared!"); +} + +void clearAll() { + clearItems(false); + flashButtonTitle("Cleared!"); +} diff --git a/examples/U8g2/Example-01_Basic/Example-01_Basic.ino b/examples/U8g2/Example-01_Basic/Example-01_Basic.ino index e4ff294..517c9ca 100644 --- a/examples/U8g2/Example-01_Basic/Example-01_Basic.ino +++ b/examples/U8g2/Example-01_Basic/Example-01_Basic.ino @@ -1,8 +1,8 @@ /* Basic menu example using GEM library. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, - and a button, pressing of which will result in int variable value printed to Serial monitor if boolean variable is set to true. + Simple one page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. U8g2lib library is used to draw menu and to detect push-buttons presses. @@ -23,7 +23,7 @@ U8G2_KS0108_128X64_1 u8g2(U8G2_R0, 8, 9, 10, 11, 12, 13, 18, 19, /*enable=*/ A0, // Create variables that will be editable through the menu and assign them initial values int number = -512; -boolean enablePrint = false; +bool enablePrint = false; // Create two menu item objects of class GEMItem, linked to number and enablePrint variables GEMItem menuItemInt("Number:", number); @@ -40,7 +40,9 @@ GEMItem menuItemButton("Print", printData); GEMPage menuPageMain("Main Menu"); // Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier -GEM_u8g2 menu(u8g2); +GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Serial communication setup diff --git a/examples/U8g2/Example-02_Blink/Example-02_Blink.ino b/examples/U8g2/Example-02_Blink/Example-02_Blink.ino index b06485a..6c35ae7 100644 --- a/examples/U8g2/Example-02_Blink/Example-02_Blink.ino +++ b/examples/U8g2/Example-02_Blink/Example-02_Blink.ino @@ -35,7 +35,7 @@ char label[GEM_STR_LEN] = "Blink!"; // Maximum length of the string should not e unsigned long previousMillis = 0; // Variable to hold current label state (visible or hidden) -boolean labelOn = false; +bool labelOn = false; // Create two menu item objects of class GEMItem, linked to interval and label variables // with validateInterval() callback function attached to interval menu item, @@ -64,7 +64,9 @@ GEMPage menuPageSettings("Settings"); // Settings submenu GEMItem menuItemMainSettings("Settings", menuPageSettings); // Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier -GEM_u8g2 menu(u8g2); +GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Serial communication setup diff --git a/examples/U8g2/Example-03_Party-Hard/Example-03_Party-Hard.ino b/examples/U8g2/Example-03_Party-Hard/Example-03_Party-Hard.ino index 69ca612..f630527 100644 --- a/examples/U8g2/Example-03_Party-Hard/Example-03_Party-Hard.ino +++ b/examples/U8g2/Example-03_Party-Hard/Example-03_Party-Hard.ino @@ -3,7 +3,7 @@ editable menu items with validation callbacks, setting readonly mode, creation of context with context.allowExit set to false in order to use push-buttons to control scene within context's loop routine. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, one option select, + Simple one page menu with one editable menu item associated with int variable, one with bool variable, one option select, and a button, pressing of which will launch an animation sequence drawn to the screen. Delay between frames is determined by value of int variable, setting of which to 0 will enable manual control of the frames through navigation push-buttons. @@ -31,7 +31,7 @@ U8G2_KS0108_128X64_2 u8g2(U8G2_R0, 8, 9, 10, 11, 12, 13, 18, 19, /*enable=*/ A0, // Create variables that will be editable through the menu and assign them initial values int interval = 200; -boolean strobe = false; +bool strobe = false; // Create variable that will be editable through option select and create associated option select byte tempo = 0; @@ -68,7 +68,9 @@ GEMItem menuItemButton("Let's Rock!", rock); GEMPage menuPageMain("Party Hard"); // Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier -GEM_u8g2 menu(u8g2); +GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Serial communication setup @@ -147,7 +149,7 @@ void drawSprite(Splash _splash) { } // Draw frame based on direction of animation -void drawFrame(boolean forward) { +void drawFrame(bool forward) { if (forward) { // Next frame currentFrame = (currentFrame == framesCount ? 1 : currentFrame+1); diff --git a/examples/U8g2/Example-04_Basic_Cyr/Example-04_Basic_Cyr.ino b/examples/U8g2/Example-04_Basic_Cyr/Example-04_Basic_Cyr.ino index 8173c20..df65116 100644 --- a/examples/U8g2/Example-04_Basic_Cyr/Example-04_Basic_Cyr.ino +++ b/examples/U8g2/Example-04_Basic_Cyr/Example-04_Basic_Cyr.ino @@ -1,8 +1,8 @@ /* Basic menu example using GEM library. Cyrillic version. - Simple one page menu with one editable menu item associated with int variable, one with boolean variable, - and a button, pressing of which will result in int variable value printed to Serial monitor if boolean variable is set to true. + Simple one page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. Note, there might be some problems with displaying Cyrillic characters in Serial monitor, in which case adjusting baud rate may help. @@ -17,8 +17,8 @@ Пример использования библиотеки GEM с поддержкой кириллицы в элементах интерфейса. - Простое одностраничное меню, состояющее из пунктов с int переменной, boolean переменной и кнопки, нажатие на которую приведёт - к печати значения int переменной в Serial monitor в случае, если значение boolean переменной установлено в true. + Простое одностраничное меню, состояющее из пунктов с int переменной, bool переменной и кнопки, нажатие на которую приведёт + к печати значения int переменной в Serial monitor в случае, если значение bool переменной установлено в true. Обратите внимание, что для стабильного отображения кириллицы в Serial monitor может потребоваться дополнительная настройка скорости передачи последовательного порта. @@ -42,7 +42,7 @@ U8G2_KS0108_128X64_1 u8g2(U8G2_R0, 8, 9, 10, 11, 12, 13, 18, 19, /*enable=*/ A0, // Create variables that will be editable through the menu and assign them initial values int number = -512; -boolean enablePrint = false; +bool enablePrint = false; // Create two menu item objects of class GEMItem, linked to number and enablePrint variables GEMItem menuItemInt("Число:", number); @@ -59,7 +59,9 @@ GEMItem menuItemButton("Вывести на печать", printData); GEMPage menuPageMain("Главное меню"); // Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier -GEM_u8g2 menu(u8g2); +GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); void setup() { // Serial communication setup diff --git a/examples/U8g2/Example-05_Encoder/Example-05_Encoder.ino b/examples/U8g2/Example-05_Encoder/Example-05_Encoder.ino new file mode 100644 index 0000000..eb98fd7 --- /dev/null +++ b/examples/U8g2/Example-05_Encoder/Example-05_Encoder.ino @@ -0,0 +1,237 @@ +/* + Basic menu example using GEM library. Using rotary encoder as an input source. + + Simple two page menu with one editable menu item associated with int variable, one with bool variable, + and a button, pressing of which will result in int variable value printed to Serial monitor if bool variable is set to true. + + Second menu page with option select is added to better demonstrate operation of the menu with rotary encoder. + + U8g2lib library is used to draw menu. + KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. + + Additional info (including the breadboard view) available on GitHub: + https://github.com/Spirik/GEM + + This example code is in the public domain. +*/ + +#include +#include + +// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button) +#define KEY_A 1 +#define KEY_B 2 +#define KEY_C 3 + +// Pins encoder is connected to +const byte channelA = 2; +const byte channelB = 3; +const byte buttonPin = 4; + +byte chanB = HIGH; // Variable to store Channel B readings + +// Array of Key objects that will link GEM key identifiers with dedicated pins +// (it is only necessary to detect signal change on a single channel of the encoder, either A or B; +// order of the channel and push-button Key objects in an array is not important) +Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}}; +//Key keys[] = {{KEY_C, buttonPin}, {KEY_A, channelA}}; + +// Create KeyDetector object +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); +// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay +// as the third argument to KeyDetector constructor. +// Make sure to adjust debounce delay to better fit your rotary encoder. +// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case). +// Analog threshold is not necessary for this example and is set to default value 16. +KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 5, /* analogThreshold= */ 16, /* pullup= */ true); + +bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers +bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times +const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms +long keyPressTime = 0; // Variable to hold time of the key press event +long now; // Variable to hold current time taken with millis() function at the beginning of loop() + +// Create an instance of the U8g2 library. +// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details). +// This instance is used to call all the subsequent U8g2 functions (internally from GEM library, +// or manually in your sketch if it is required). +// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected +U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); + +// Create variables that will be editable through the menu and assign them initial values +int number = -512; +bool enablePrint = false; + +// Create variable that will be editable through option select and create associated option select. +// This variable will be passed to menu.invertKeysDuringEdit(), and naturally can be presented as a bool, +// but is declared as a byte type to be used in an option select rather than checkbox (for demonstration purposes) +byte invert = 1; +SelectOptionByte selectInvertOptions[] = {{"Invert", 1}, {"Normal", 0}}; +GEMSelect selectInvert(sizeof(selectInvertOptions)/sizeof(SelectOptionByte), selectInvertOptions); + +// Create menu item for option select with applyInvert() callback function +void applyInvert(); // Forward declaration +GEMItem menuItemInvert("Chars order:", invert, selectInvert, applyInvert); + +// Create two menu item objects of class GEMItem, linked to number and enablePrint variables +GEMItem menuItemInt("Number:", number); +GEMItem menuItemBool("Enable print:", enablePrint); + +// Create menu button that will trigger printData() function. It will print value of our number variable +// to Serial monitor if enablePrint is true. We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void printData(); // Forward declaration +GEMItem menuItemButton("Print", printData); + +// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. +// Menu can have multiple menu pages (linked to each other) with multiple menu items each +GEMPage menuPageMain("Main Menu"); // Main page +GEMPage menuPageSettings("Settings"); // Settings submenu + +// Create menu item linked to Settings menu page +GEMItem menuItemMainSettings("Settings", menuPageSettings); + +// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier +GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO); +// Which is equivalent to the following call (you can adjust parameters to better fit your screen if necessary): +// GEM_u8g2 menu(u8g2, /* menuPointerType= */ GEM_POINTER_ROW, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86); + +void setup() { + // Pin modes + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + pinMode(buttonPin, INPUT_PULLUP); + + // Serial communication setup + Serial.begin(115200); + + // U8g2 library init. + u8g2.begin(); + + // Turn inverted order of characters during edit mode on (feels more natural when using encoder) + menu.invertKeysDuringEdit(invert); + + // Menu init, setup and draw + menu.init(); + setupMenu(); + menu.drawMenu(); + + Serial.println("Initialized"); +} + +void setupMenu() { + // Add menu items to Settings menu page + menuPageSettings.addMenuItem(menuItemInvert); + + // Add menu items to menu page + menuPageMain.addMenuItem(menuItemMainSettings); + menuPageMain.addMenuItem(menuItemInt); + menuPageMain.addMenuItem(menuItemBool); + menuPageMain.addMenuItem(menuItemButton); + + // Specify parent menu page for the Settings menu page + menuPageSettings.setParentMenuPage(menuPageMain); + + // Add menu page to menu and set it as current + menu.setMenuPageCurrent(menuPageMain); +} + +void loop() { + // Get current time to use later on + now = millis(); + + // If menu is ready to accept button press... + if (menu.readyForKey()) { + chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm + // ...detect key press using KeyDetector library + // and pass pressed button to menu + myKeyDetector.detect(); + + switch (myKeyDetector.trigger) { + case KEY_A: + // Signal from Channel A of encoder was detected + if (chanB == LOW) { + // If channel B is low then the knob was rotated CCW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... + Serial.println("Rotation CCW with button pressed"); + menu.registerKeyPress(GEM_KEY_LEFT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_UP otherwise + Serial.println("Rotation CCW"); + menu.registerKeyPress(GEM_KEY_UP); + } + } else { + // If channel B is high then the knob was rotated CW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... + Serial.println("Rotation CW with button pressed"); + menu.registerKeyPress(GEM_KEY_RIGHT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_DOWN otherwise + Serial.println("Rotation CW"); + menu.registerKeyPress(GEM_KEY_DOWN); + } + } + break; + case KEY_C: + // Button was pressed + Serial.println("Button pressed"); + // Save current time as a time of the key press event + keyPressTime = now; + break; + } + switch (myKeyDetector.triggerRelease) { + case KEY_C: + // Button was released + Serial.println("Button released"); + if (!secondaryPressed) { + // If button was not used as a modifier to rotation action... + if (now <= keyPressTime + keyPressDelay) { + // ...and if not enough time passed since keyPressTime, + // treat key that was pressed as Ok button + menu.registerKeyPress(GEM_KEY_OK); + } + } + secondaryPressed = false; + cancelPressed = false; + break; + } + // After keyPressDelay passed since keyPressTime + if (now > keyPressTime + keyPressDelay) { + switch (myKeyDetector.current) { + case KEY_C: + if (!secondaryPressed && !cancelPressed) { + // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet + Serial.println("Button remained pressed"); + // Treat key that was pressed as Cancel button + menu.registerKeyPress(GEM_KEY_CANCEL); + cancelPressed = true; + } + break; + } + } + } +} + +void printData() { + // If enablePrint flag is set to true (checkbox on screen is checked)... + if (enablePrint) { + // ...print the number to Serial + Serial.print("Number is: "); + Serial.println(number); + } else { + Serial.println("Printing is disabled, sorry:("); + } +} + +void applyInvert() { + menu.invertKeysDuringEdit(invert); + // Print invert variable to Serial + Serial.print("Invert: "); + Serial.println(invert); +} \ No newline at end of file diff --git a/examples/U8g2/Example-06_Todo-List/Example-06_Todo-List.ino b/examples/U8g2/Example-06_Todo-List/Example-06_Todo-List.ino new file mode 100644 index 0000000..b737e05 --- /dev/null +++ b/examples/U8g2/Example-06_Todo-List/Example-06_Todo-List.ino @@ -0,0 +1,477 @@ +/* + Using GEM to create Todo list utilizing (god-forbidden) dynamic memory allocation (`new` and `delete`). + Using rotary encoder as an input source. Todo items can be dynamically added to the list, + marked completed and cleared (removed from the list). Additional settings are provided + (e.g. changing menu pointer style and order of characters in edit mode). + + Note, that generally it is not recommended to implement dynamic memory allocation in microcontroller-based + projects for a number of reasons (mostly due to memory limitations and lack of supervisory OS to handle + memory management). Consider this example as an experiment and merely demonstration of some of the GEM + features, rather than a guide on how to manage dynamic memory in your project. + + U8g2lib library is used to draw menu. + KeyDetector library (version 1.2.0 or later) is used to detect rotary encoder operation. + + Points of improvement to consider: + - Prevent adding new Todo items if insufficient amount of RAM is available + - Add button to Uncheck all and/or Check all Todo items + - Use external storage (e.g. SD Card) to store data + - Make portable by adding battery + + This example uses the same schematics/breadboard as Example-05_Encoder (supplied with GEM). + + Additional info (including the breadboard view) available on GitHub: + https://github.com/Spirik/GEM + + This example code is in the public domain. +*/ + +#include +#include + +//====================== CLASSES + +// Class representing Todo item +class TodoItem { + public: + /* + @param 'title_' - title of Todo item + */ + TodoItem(char* title_){ + strcpy(title, title_); + menuItem = new GEMItem(title, completed); + }; + + char title[GEM_STR_LEN]; // Title of Todo item + bool completed = false; // Checkbox status + GEMItem* menuItem = nullptr; // Pointer to corresponding menu item +}; + +//====================== MISC + +// Custom splash +#define splashWidth 27 +#define splashHeight 8 +static const unsigned char splashBits [] U8X8_PROGMEM = { + 0x1F, 0xC7, 0xC3, 0x01, 0x80, 0x08, 0x24, 0x02, 0x84, 0x48, 0x24, 0x02, + 0x84, 0x48, 0x24, 0x02, 0x84, 0x48, 0x24, 0x02, 0x84, 0x48, 0x24, 0x02, + 0x04, 0xC7, 0xC3, 0x01, 0x00, 0x00, 0x00, 0x00 +}; + +//====================== WORKING WITH ENCODER + +// Define signal identifiers for three outputs of encoder (channel A, channel B and a push-button) +#define KEY_A 1 +#define KEY_B 2 +#define KEY_C 3 + +// Pins encoder is connected to +const byte channelA = 2; +const byte channelB = 3; +const byte buttonPin = 4; + +byte chanB = HIGH; // Variable to store Channel B readings + +// Array of Key objects that will link GEM key identifiers with dedicated pins +// (it is only necessary to detect signal change on a single channel of the encoder, either A or B; +// order of the channel and push-button Key objects in an array is not important) +Key keys[] = {{KEY_A, channelA}, {KEY_C, buttonPin}}; + +// Create KeyDetector object +// KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key)); +// To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay +// as the third argument to KeyDetector constructor. +// Make sure to adjust debounce delay to better fit your rotary encoder. +// Also it is possible to enable pull-up mode when buttons wired with pull-up resistors (as in this case). +// Analog threshold is not necessary for this example and is set to default value 16. +KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), /* debounceDelay= */ 10, /* analogThreshold= */ 16, /* pullup= */ true); + +bool secondaryPressed = false; // If encoder rotated while key was being pressed; used to prevent unwanted triggers +bool cancelPressed = false; // Flag indicating that Cancel action was triggered, used to prevent it from triggering multiple times +const int keyPressDelay = 1000; // How long to hold key in pressed state to trigger Cancel action, ms +long keyPressTime = 0; // Variable to hold time of the key press event +long now; // Variable to hold current time taken with millis() function at the beginning of loop() + +//====================== OBTAINING RAM STATUS + +// Variable to store free RAM. It is an int, so overflows and rollover may occur, in that case, free RAM won't be displayed +int freeRam; +GEMItem menuItemRam("Free RAM:", freeRam, GEM_READONLY); // Menu item associated with it + +// Free RAM calculations +// (based on https://docs.arduino.cc/learn/programming/memory-guide and https://github.com/mpflaga/Arduino-MemoryFree/) +#if defined(__arm__) && !defined(ARDUINO_ARCH_RP2040) +// ARM (except RP2040, which won't display correct values, probably due to internal implementation) + +extern "C" char* sbrk(int incr); + +void calculateFreeRam() { + freeRam = getFreeRam(); +} + +int getFreeRam() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} + +#elif defined(ARDUINO_ARCH_AVR) +// ARM + +void calculateFreeRam() { + freeRam = getFreeRam(); +} + +int getFreeRam() { + extern int __heap_start,*__brkval; + int v; + return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int) __brkval); +} + +#elif defined(ARDUINO_ARCH_ESP32) +// ESP32 + +void calculateFreeRam() { + freeRam = ESP.getFreeHeap(); +} + +#else +// Correct detection of free RAM not implemented + +void calculateFreeRam() { + freeRam = -1; +} + +#endif + +//====================== DISPLAY + +// Create an instance of the U8g2 library. +// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details). +// This instance is used to call all the subsequent U8g2 functions (internally from GEM library, +// or manually in your sketch if it is required). +// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected +U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); + +//====================== MENU AND CORRESPONDING ELEMENTS + +// Create variable that will be editable through option select and create associated option select. +// This variable will be passed to menu.invertKeysDuringEdit(), and naturally can be presented as a boolean, +// but is declared as a byte type to be used in an option select rather than checkbox (for demonstration purposes) +byte invert = 1; +SelectOptionByte selectInvertOptions[] = {{"Invert", 1}, {"Normal", 0}}; +GEMSelect selectInvert(sizeof(selectInvertOptions)/sizeof(SelectOptionByte), selectInvertOptions); + +// Create menu item for option select with applyInvert() callback function +void applyInvert(); // Forward declaration +GEMItem menuItemInvert("Chars order:", invert, selectInvert, applyInvert); + +// Create variable holding appearance of menu pointer that will be editable through option select and create associated option select. +byte menuPointer = GEM_POINTER_ROW; +SelectOptionByte selectMenuPointerOptions[] = {{"Row", GEM_POINTER_ROW}, {"Dash", GEM_POINTER_DASH}}; +GEMSelect selectMenuPointer(sizeof(selectMenuPointerOptions)/sizeof(SelectOptionByte), selectMenuPointerOptions); + +// Create menu item for option select with applyMenuPointer() callback function +void applyMenuPointer(); // Forward declaration +GEMItem menuItemMenuPointer("Menu pointer:", menuPointer, selectMenuPointer, applyMenuPointer); + +// Create variable that will temporarily hold title of new Todo list item +char newItemTitle[GEM_STR_LEN]; + +// Create menu item for title of new Todo list item +void editTitle(); // Forward declaration +GEMItem menuItemTitle("Title:", newItemTitle, editTitle); + +// Create menu button that will trigger addItem() function. It will add new record to Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void addItem(); // Forward declaration +GEMItem menuItemButtonAdd("Add", addItem); + +// Create menu button that will trigger clearItems() function. It will remove completed items from Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void clearCompleted(); // Forward declaration +GEMItem menuItemButtonClear("Clear completed", clearCompleted); + +// Create menu button that will trigger clearAll() function. It will remove all items from Todo list. +// We will write (define) this function later. However, we should +// forward-declare it in order to pass to GEMItem constructor +void clearAll(); // Forward declaration +GEMItem menuItemButtonClearAll("Clear all", clearAll); + +// Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level. +// Menu can have multiple menu pages (linked to each other) with multiple menu items each +GEMPage menuPageMain("Main Menu"); // Main page +GEMPage menuPageList("Todo", menuPageMain); // Todo list submenu +GEMPage menuPageAdd("Add Item", menuPageList); // Add item submenu +GEMPage menuPageManage("Manage", menuPageMain); // Manage submenu +GEMPage menuPageSettings("Settings", menuPageMain); // Settings submenu + +// Create menu item links to submenu pages +GEMItem menuItemLinkList("List", menuPageList); // Create menu item linked to List menu page +GEMItem menuItemLinkAdd("Add+", menuPageAdd); // Create menu item linked to Add menu page +GEMItem menuItemLinkManage("Manage", menuPageManage); // Create menu item linked to Manage menu page +GEMItem menuItemLinkSettings("Settings", menuPageSettings); // Create menu item linked to Settings menu page + +// Create GEMAppearance objects +GEMAppearance appearanceGeneral = { /* menuPointerType= */ menuPointer, /* menuItemsPerScreen= */ GEM_ITEMS_COUNT_AUTO, /* menuItemHeight= */ 10, /* menuPageScreenTopOffset= */ 10, /* menuValuesLeftOffset= */ 86}; +GEMAppearance appearanceList = appearanceGeneral; +GEMAppearance appearanceAdd = appearanceGeneral; + +// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier +GEM_u8g2 menu(u8g2, appearanceGeneral); + +void setup() { + // Pin modes + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + pinMode(buttonPin, INPUT_PULLUP); + + // Serial communication setup + Serial.begin(115200); + + // U8g2 library init. + u8g2.begin(); + + menu + // Turn inverted order of characters during edit mode on (feels more natural when using encoder) + .invertKeysDuringEdit(invert) + // Set custom splash + .setSplash(splashWidth, splashHeight, splashBits) + // Menu init, setup and draw + .init(); + setupMenu(); + + calculateFreeRam(); + if (freeRam < 0) { + // Hide RAM counter if not available or rolled over an int value + menuItemRam.hide(); + } + + menu.drawMenu(); + + Serial.println(F("Initialized")); +} + +void setupMenu() { + // Add menu items to menu page + menuPageMain + .addMenuItem(menuItemLinkList) + .addMenuItem(menuItemLinkManage) + .addMenuItem(menuItemLinkSettings); + + appearanceList.menuValuesLeftOffset = 118; + + // Add menu items to List menu page + menuPageList + .setAppearance(&appearanceList) + .addMenuItem(menuItemLinkAdd); + + // Turn on adjusted order of ASCII characters when editing title + menuItemTitle.setAdjustedASCIIOrder(); + + appearanceAdd.menuValuesLeftOffset = 46; + + // Add menu items to Add menu page + menuPageAdd + .setAppearance(&appearanceAdd) + .addMenuItem(menuItemTitle) + .addMenuItem(menuItemButtonAdd); + + // Add menu items to Manage menu page + menuPageManage + .addMenuItem(menuItemRam) + .addMenuItem(menuItemButtonClear) + .addMenuItem(menuItemButtonClearAll); + + // Add menu items to Settings menu page + menuPageSettings + .addMenuItem(menuItemInvert) + .addMenuItem(menuItemMenuPointer); + + // Set List page as a starting one + menu.setMenuPageCurrent(menuPageList); + + // Hide Add button by default (until Todo item title is entered) + menuItemButtonAdd.hide(); +} + +// loop() is primarily used to manage rotary encoder operation, +// with six push-buttons instead it is much shorter +void loop() { + // Get current time to use later on + now = millis(); + + // If menu is ready to accept button press... + if (menu.readyForKey()) { + chanB = digitalRead(channelB); // Reading Channel B signal beforehand to account for possible delays due to polling nature of KeyDetector algorithm + // ...detect key press using KeyDetector library + // and pass pressed button to menu + myKeyDetector.detect(); + + // Calculate RAM each loop iteration + // calculateFreeRam(); + + switch (myKeyDetector.trigger) { + case KEY_A: + // Signal from Channel A of encoder was detected + if (chanB == LOW) { + // If channel B is low then the knob was rotated CCW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_LEFT,... + menu.registerKeyPress(GEM_KEY_LEFT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_UP otherwise + menu.registerKeyPress(GEM_KEY_UP); + } + } else { + // If channel B is high then the knob was rotated CW + if (myKeyDetector.current == KEY_C) { + // If push-button was pressed at that time, then treat this action as GEM_KEY_RIGHT,... + menu.registerKeyPress(GEM_KEY_RIGHT); + // Button was in a pressed state during rotation of the knob, acting as a modifier to rotation action + secondaryPressed = true; + } else { + // ...or GEM_KEY_DOWN otherwise + menu.registerKeyPress(GEM_KEY_DOWN); + } + } + break; + case KEY_C: + // Button was pressed + // Save current time as a time of the key press event + keyPressTime = now; + break; + } + switch (myKeyDetector.triggerRelease) { + case KEY_C: + // Button was released + if (!secondaryPressed) { + // If button was not used as a modifier to rotation action... + if (now <= keyPressTime + keyPressDelay) { + // ...and if not enough time passed since keyPressTime, + // treat key that was pressed as Ok button + menu.registerKeyPress(GEM_KEY_OK); + } + } + secondaryPressed = false; + cancelPressed = false; + break; + } + // After keyPressDelay passed since keyPressTime + if (now > keyPressTime + keyPressDelay) { + switch (myKeyDetector.current) { + case KEY_C: + if (!secondaryPressed && !cancelPressed) { + // If button was not used as a modifier to rotation action, and Cancel action was not triggered yet + // Treat key that was pressed as Cancel button + menu.registerKeyPress(GEM_KEY_CANCEL); + cancelPressed = true; + } + break; + } + } + } +} + +void flashButtonTitle(const char* title, bool redraw = true) { + GEMItem* menuItemButtonTmp = menu.getCurrentMenuPage()->getCurrentMenuItem(); + const char* titleOrig = menuItemButtonTmp->getTitle(); + menuItemButtonTmp->setTitle(title); + menu.drawMenu(); + delay(1000); + menuItemButtonTmp->setTitle(titleOrig); + if (redraw) { + menu.drawMenu(); + } +} + +void applyInvert() { + menu.invertKeysDuringEdit(invert); + + // Print invert variable to Serial + Serial.print(F("Invert: ")); + Serial.println(invert); +} + +void applyMenuPointer() { + appearanceGeneral.menuPointerType = menuPointer; + menu.setAppearance(appearanceGeneral); // Need to call setAppearance() when changing general appearance + appearanceList.menuPointerType = menuPointer; // No need to call setAppearance() when changing apperance of menu pages, because it submitted as a pointer + appearanceAdd.menuPointerType = menuPointer; + + // Print invert variable to Serial + Serial.print(F("Menu pointer: ")); + Serial.println(menuPointer); +} + +void editTitle() { + menuItemButtonAdd.hide(newItemTitle[0] == '\0'); +} + +void addItem() { + if (newItemTitle[0] != '\0') { + Serial.print(F("Add Item: ")); + Serial.println(newItemTitle); + + // Creating new TodoItem object and adding corresponding menu item to menu page + menuItemLinkAdd.hide(); // Temporarily hide Add button to add new item at the end of the list (but before hidden button) + TodoItem* tempItem = new TodoItem(newItemTitle); + tempItem->menuItem->setCallbackVal(tempItem); // Save pointer to Todo item in a GEMCallbackData struct inside corresponding menu item + menuPageList.addMenuItem(*tempItem->menuItem, GEM_LAST_POS, GEM_ITEMS_VISIBLE); + menuItemLinkAdd.show(); + memset(newItemTitle, '\0', GEM_STR_LEN - 1); + + // Temporarily change title of Add button, but w/o redrawing menu (because we will hide it) + flashButtonTitle("Item added!", false); + + menuItemButtonAdd.hide(); + } + calculateFreeRam(); + menu.drawMenu(); +} + +void clearItems(bool onlyCompleted = true) { + GEMItem* menuItemTmp = menuPageList.getMenuItem(1); // Get first Todo item in a list to start traversing through menu items + Serial.println(F("Clearing items:")); + while (menuItemTmp->getLinkedVariablePointer() != nullptr) { + GEMItem* nextItem = menuItemTmp->getMenuItemNext(); // Save pointer to a next item + bool completed = *(bool*)menuItemTmp->getLinkedVariablePointer(); // Save completed status + if (completed || !onlyCompleted) { + // If linked boolean variable is true, then consider Todo item completed and ready to be removed + // (and remove it anyway in case if onlyCompleted set to false) + Serial.print(completed ? "[x]" : "[ ]"); + Serial.println(menuItemTmp->getTitle()); + TodoItem* todoItemTmp = (TodoItem*)menuItemTmp->getCallbackData().valPointer; // Get pointer to corresponsing TodoItem object + menuItemTmp->remove(); // Remove menu item from menu page + delete menuItemTmp; // Delete GEMItem object + delete todoItemTmp; // Delete TodoItem object + /* + Note 1: sometimes (e.g. on ARM-based MCUs, but not on AVR or ESP32) deleting completed Todo items + doesn't immediately reflect on the amount of free RAM (as reported by getFreeRam()), however + if new Todo item is created afterwards (after deleting completed one) free RAM counter won't change. + That shows that deleteing objects is actually works (just not always reflected on the visible amount of free RAM, + probably for reasons discussed here: https://forum.arduino.cc/t/memory-no-getting-cleaned-up-after-delete/894404), + and new item presumably occupies previously freed memory. This may be related to so-called "buried heap space". + Note 2: amount of displayed free RAM may change after moving cursor from button after clearing, for the same amount every + time for some reason, even if no actual Todo items was deleted (it may something to do with processes needed to redraw + menu and/or stack allocation for clearItems() call). However, previous statement (Note 1) still holds. + */ + } + menuItemTmp = nextItem; + } + calculateFreeRam(); + menu.drawMenu(); +} + +void clearCompleted() { + clearItems(); + flashButtonTitle("Cleared!"); +} + +void clearAll() { + clearItems(false); + flashButtonTitle("Cleared!"); +} diff --git a/keywords.txt b/keywords.txt index 3641b60..9e84179 100644 --- a/keywords.txt +++ b/keywords.txt @@ -8,34 +8,55 @@ GEM KEYWORD1 GEM_u8g2 KEYWORD1 +GEM_adafruit_gfx KEYWORD1 GEMItem KEYWORD1 GEMPage KEYWORD1 GEMSelect KEYWORD1 GEMCallbackData KEYWORD1 +GEMAppearance KEYWORD1 +GEMContext KEYWORD1 +AppContext KEYWORD1 Splash KEYWORD1 FontSize KEYWORD1 FontFamilies KEYWORD1 -AppContext KEYWORD1 SelectOptionInt KEYWORD1 SelectOptionByte KEYWORD1 SelectOptionChar KEYWORD1 SelectOptionFloat KEYWORD1 SelectOptionDouble KEYWORD1 +GEMSpinner KEYWORD1 +GEMSpinnerBoundaries KEYWORD1 +GEMSpinnerBoundariesByte KEYWORD1 +GEMSpinnerBoundariesInt KEYWORD1 +GEMSpinnerBoundariesFloat KEYWORD1 +GEMSpinnerBoundariesDouble KEYWORD1 +GEMSpinnerValue KEYWORD1 #################################################### # Methods and Functions (KEYWORD2) #################################################### +setAppearance KEYWORD2 +getCurrentAppearance KEYWORD2 setSplash KEYWORD2 setSplashDelay KEYWORD2 hideVersion KEYWORD2 +setFontBig KEYWORD2 +setFontSmall KEYWORD2 setForegroundColor KEYWORD2 setBackgroundColor KEYWORD2 +invertKeysDuringEdit KEYWORD2 +setTextSize KEYWORD2 +enableUTF8 KEYWORD2 enableCyrillic KEYWORD2 init KEYWORD2 reInit KEYWORD2 setMenuPageCurrent KEYWORD2 +getCurrentMenuPage KEYWORD2 drawMenu KEYWORD2 +setDrawMenuCallback KEYWORD2 +removeDrawMenuCallback KEYWORD2 +isEditMode KEYWORD2 readyForKey KEYWORD2 registerKeyPress KEYWORD2 clearContext KEYWORD2 @@ -43,12 +64,18 @@ setCallbackVal KEYWORD2 getCallbackData KEYWORD2 setTitle KEYWORD2 getTitle KEYWORD2 +getMenuItem KEYWORD2 +getCurrentMenuItem KEYWORD2 +getCurrentMenuItemIndex KEYWORD2 +getMenuItemNext KEYWORD2 setPrecision KEYWORD2 +setAdjustedASCIIOrder KEYWORD2 setReadonly KEYWORD2 getReadonly KEYWORD2 hide KEYWORD2 show KEYWORD2 getHidden KEYWORD2 +remove KEYWORD2 getLinkedVariablePointer KEYWORD2 addMenuItem KEYWORD2 setParentMenuPage KEYWORD2 @@ -88,10 +115,12 @@ GEM_ITEMS_COUNT_AUTO LITERAL1 GEM_VAL_INTEGER LITERAL1 GEM_VAL_BYTE LITERAL1 GEM_VAL_CHAR LITERAL1 +GEM_VAL_BOOL LITERAL1 GEM_VAL_BOOLEAN LITERAL1 GEM_VAL_SELECT LITERAL1 GEM_VAL_FLOAT LITERAL1 GEM_VAL_DOUBLE LITERAL1 +GEM_VAL_SPINNER LITERAL1 GEM_KEY_NONE LITERAL1 GEM_KEY_UP LITERAL1 @@ -108,3 +137,10 @@ GEM_ITEM_BUTTON LITERAL1 GEM_READONLY LITERAL1 GEM_HIDDEN LITERAL1 + +GEM_LAST_POS LITERAL1 +GEM_ITEMS_TOTAL LITERAL1 +GEM_ITEMS_VISIBLE LITERAL1 + +GEM_FONT_BIG LITERAL1 +GEM_FONT_SMALL LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index c672753..3ca514c 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,9 @@ name=GEM -version=1.4.0 +version=1.6.0 author=Alexander 'Spirik' Spiridonov maintainer=Alexander 'Spirik' Spiridonov sentence=A library for creation of graphic multi-level menu. -paragraph=Features editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions. +paragraph=Features editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions. category=Display url=https://github.com/Spirik/GEM architectures=* diff --git a/src/GEM.cpp b/src/GEM.cpp index 64241be..cfde31a 100644 --- a/src/GEM.cpp +++ b/src/GEM.cpp @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -39,7 +39,7 @@ // AVR-based Arduinos have suppoort for dtostrf, some others may require manual inclusion (e.g. SAMD), // see https://github.com/plotly/arduino-api/issues/38#issuecomment-108987647 -#if defined(GEM_SUPPORT_FLOAT_EDIT) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)) +#if defined(GEM_SUPPORT_FLOAT_EDIT) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_NRF52840)) #include #endif @@ -60,6 +60,9 @@ #define GEM_CHAR_CODE_UNDERSCORE 95 #define GEM_CHAR_CODE_LINE 124 #define GEM_CHAR_CODE_TILDA 126 +#define GEM_CHAR_CODE_BANG 33 +#define GEM_CHAR_CODE_a 97 +#define GEM_CHAR_CODE_ACCENT 96 // Sprite of the default GEM _splash screen (GEM logo v1) /* @@ -104,14 +107,13 @@ static const uint8_t selectArrows [] PROGMEM = { GEM::GEM(GLCD& glcd_, byte menuPointerType_, byte menuItemsPerScreen_, byte menuItemHeight_, byte menuPageScreenTopOffset_, byte menuValuesLeftOffset_) : _glcd(glcd_) - , _menuPointerType(menuPointerType_) - , _menuItemsPerScreen(menuItemsPerScreen_) - , _menuItemHeight(menuItemHeight_) - , _menuPageScreenTopOffset(menuPageScreenTopOffset_) - , _menuValuesLeftOffset(menuValuesLeftOffset_) { - _menuItemFontSize = _menuItemHeight >= 8 ? 0 : 1; - _menuItemInsetOffset = (_menuItemHeight - _menuItemFont[_menuItemFontSize].height) / 2; + _appearance.menuPointerType = menuPointerType_; + _appearance.menuItemsPerScreen = menuItemsPerScreen_; + _appearance.menuItemHeight = menuItemHeight_; + _appearance.menuPageScreenTopOffset = menuPageScreenTopOffset_; + _appearance.menuValuesLeftOffset = menuValuesLeftOffset_; + _appearanceCurrent = &_appearance; _splash = logo; clearContext(); _editValueMode = false; @@ -120,21 +122,69 @@ GEM::GEM(GLCD& glcd_, byte menuPointerType_, byte menuItemsPerScreen_, byte menu _valueSelectNum = -1; } +GEM::GEM(GLCD& glcd_, GEMAppearance appearance_) + : _glcd(glcd_) + , _appearance(appearance_) +{ + _appearanceCurrent = &_appearance; + _splash = logo; + clearContext(); + _editValueMode = false; + _editValueCursorPosition = 0; + memset(_valueString, '\0', GEM_STR_LEN - 1); + _valueSelectNum = -1; +} + +//====================== APPEARANCE OPERATIONS + +GEM& GEM::setAppearance(GEMAppearance appearance) { + _appearance = appearance; + return *this; +} + +GEMAppearance* GEM::getCurrentAppearance() { + return (_menuPageCurrent != nullptr && _menuPageCurrent->_appearance != nullptr) ? _menuPageCurrent->_appearance : &_appearance; +} + +byte GEM::getMenuItemsPerScreen() { + return getCurrentAppearance()->menuItemsPerScreen == GEM_ITEMS_COUNT_AUTO ? (_glcd.ydim - getCurrentAppearance()->menuPageScreenTopOffset) / getCurrentAppearance()->menuItemHeight : getCurrentAppearance()->menuItemsPerScreen; +} + +byte GEM::getMenuItemFontSize() { + return getCurrentAppearance()->menuItemHeight >= 8 ? 0 : 1; +} + +byte GEM::getMenuItemTitleLength() { + return (getCurrentAppearance()->menuValuesLeftOffset - 5) / _menuItemFont[getMenuItemFontSize()].width; +} + +byte GEM::getMenuItemValueLength() { + return (_glcd.xdim - getCurrentAppearance()->menuValuesLeftOffset - 6) / _menuItemFont[getMenuItemFontSize()].width; +} + //====================== INIT OPERATIONS -void GEM::setSplash(const uint8_t *sprite) { +GEM& GEM::setSplash(const uint8_t *sprite) { _splash = sprite; + return *this; } -void GEM::setSplashDelay(uint16_t value) { +GEM& GEM::setSplashDelay(uint16_t value) { _splashDelay = value; + return *this; } -void GEM::hideVersion(boolean flag) { +GEM& GEM::hideVersion(bool flag) { _enableVersion = !flag; + return *this; +} + +GEM& GEM::invertKeysDuringEdit(bool invert) { + _invertKeysDuringEdit = invert; + return *this; } -void GEM::init() { +GEM& GEM::init() { _glcd.loadSprite_P(GEM_SPR_ARROW_RIGHT, arrowRight); _glcd.loadSprite_P(GEM_SPR_ARROW_LEFT, arrowLeft); _glcd.loadSprite_P(GEM_SPR_ARROW_BTN, arrowBtn); @@ -148,12 +198,6 @@ void GEM::init() { _glcd.set(GLCD_ID_SCROLL, 0); _glcd.clearScreen(); - _menuItemTitleLength = (_menuValuesLeftOffset - 5) / _menuItemFont[_menuItemFontSize].width; - _menuItemValueLength = (_glcd.xdim - _menuValuesLeftOffset - 6) / _menuItemFont[_menuItemFontSize].width; - if (_menuItemsPerScreen == GEM_ITEMS_COUNT_AUTO) { - _menuItemsPerScreen = (_glcd.ydim - _menuPageScreenTopOffset) / _menuItemHeight; - } - if (_splashDelay > 0) { _glcd.bitblt_P(_glcd.xdim/2-(pgm_read_byte(_splash)+1)/2, _glcd.ydim/2-(pgm_read_byte(_splash+1)+1)/2, GLCD_MODE_NORMAL, _splash); @@ -175,44 +219,67 @@ void GEM::init() { } } + + return *this; } -void GEM::reInit() { +GEM& GEM::reInit() { _glcd.drawMode(GLCD_MODE_NORMAL); _glcd.fontMode(GLCD_MODE_NORMAL); _glcd.set(GLCD_ID_CRLF, 0); _glcd.set(GLCD_ID_SCROLL, 0); _glcd.clearScreen(); + return *this; } -void GEM::setMenuPageCurrent(GEMPage& menuPageCurrent) { +GEM& GEM::setMenuPageCurrent(GEMPage& menuPageCurrent) { _menuPageCurrent = &menuPageCurrent; + return *this; +} + +GEMPage* GEM::getCurrentMenuPage() { + return _menuPageCurrent; } //====================== CONTEXT OPERATIONS -void GEM::clearContext() { +GEM& GEM::clearContext() { context.loop = nullptr; context.enter = nullptr; context.exit = nullptr; context.allowExit = true; + return *this; } //====================== DRAW OPERATIONS -void GEM::drawMenu() { +GEM& GEM::drawMenu() { _glcd.clearScreen(); drawTitleBar(); printMenuItems(); drawMenuPointer(); drawScrollbar(); + if (drawMenuCallback != nullptr) { + drawMenuCallback(); + } + return *this; +} + +GEM& GEM::setDrawMenuCallback(void (*drawMenuCallback_)()) { + drawMenuCallback = drawMenuCallback_; + return *this; +} + +GEM& GEM::removeDrawMenuCallback() { + drawMenuCallback = nullptr; + return *this; } void GEM::drawTitleBar() { _glcd.fontFace(1); _glcd.setXY(5,1); _glcd.putstr((char*)_menuPageCurrent->title); - _glcd.fontFace(_menuItemFontSize); + _glcd.fontFace(getMenuItemFontSize()); } void GEM::printMenuItemString(const char* str, byte num, byte startPos) { @@ -224,83 +291,115 @@ void GEM::printMenuItemString(const char* str, byte num, byte startPos) { } void GEM::printMenuItemTitle(const char* str, int offset) { - printMenuItemString(str, _menuItemTitleLength + offset); + printMenuItemString(str, getMenuItemTitleLength() + offset); } void GEM::printMenuItemValue(const char* str, int offset, byte startPos) { - printMenuItemString(str, _menuItemValueLength + offset, startPos); + printMenuItemString(str, getMenuItemValueLength() + offset, startPos); } void GEM::printMenuItemFull(const char* str, int offset) { - printMenuItemString(str, _menuItemTitleLength + _menuItemValueLength + offset); + printMenuItemString(str, getMenuItemTitleLength() + getMenuItemValueLength() + offset); } -byte GEM::getMenuItemInsetOffset(boolean forSprite) { - return _menuItemInsetOffset + (forSprite ? (_menuItemFontSize ? -1 : 0) : 0 ); // With additional offset for 6x8 sprites to compensate for smaller font size +byte GEM::getMenuItemInsetOffset(bool forSprite) { + byte menuItemFontSize = getMenuItemFontSize(); + byte menuItemInsetOffset = (getCurrentAppearance()->menuItemHeight - _menuItemFont[menuItemFontSize].height) / 2; + return menuItemInsetOffset + (forSprite ? (menuItemFontSize ? -1 : 0) : 0 ); // With additional offset for 6x8 sprites to compensate for smaller font size } -byte GEM::getCurrentItemTopOffset(boolean withInsetOffset, boolean forSprite) { - return (_menuPageCurrent->currentItemNum % _menuItemsPerScreen) * _menuItemHeight + _menuPageScreenTopOffset + (withInsetOffset ? getMenuItemInsetOffset(forSprite) : 0); +byte GEM::getCurrentItemTopOffset(bool withInsetOffset, bool forSprite) { + return (_menuPageCurrent->currentItemNum % getMenuItemsPerScreen()) * getCurrentAppearance()->menuItemHeight + getCurrentAppearance()->menuPageScreenTopOffset + (withInsetOffset ? getMenuItemInsetOffset(forSprite) : 0); } void GEM::printMenuItems() { - byte currentPageScreenNum = _menuPageCurrent->currentItemNum / _menuItemsPerScreen; - GEMItem* menuItemTmp = _menuPageCurrent->getMenuItem(currentPageScreenNum * _menuItemsPerScreen); - byte y = _menuPageScreenTopOffset; + byte menuItemsPerScreen = getMenuItemsPerScreen(); + byte currentPageScreenNum = _menuPageCurrent->currentItemNum / menuItemsPerScreen; + GEMItem* menuItemTmp = _menuPageCurrent->getMenuItem(currentPageScreenNum * menuItemsPerScreen); + byte y = getCurrentAppearance()->menuPageScreenTopOffset; byte i = 0; - while (menuItemTmp != nullptr && i < _menuItemsPerScreen) { + while (menuItemTmp != nullptr && i < menuItemsPerScreen) { _glcd.setY(y + getMenuItemInsetOffset()); byte yDraw = y + getMenuItemInsetOffset(true); switch (menuItemTmp->type) { case GEM_ITEM_VAL: - _glcd.setX(5); - if (menuItemTmp->readonly) { - printMenuItemTitle(menuItemTmp->title, -1); - _glcd.putstr((char*)"^"); - } else { - printMenuItemTitle(menuItemTmp->title); - } - _glcd.setX(_menuValuesLeftOffset); - switch (menuItemTmp->linkedType) { - case GEM_VAL_INTEGER: - itoa(*(int*)menuItemTmp->linkedVariable, _valueString, 10); - printMenuItemValue(_valueString); - break; - case GEM_VAL_BYTE: - itoa(*(byte*)menuItemTmp->linkedVariable, _valueString, 10); - printMenuItemValue(_valueString); - break; - case GEM_VAL_CHAR: - printMenuItemValue((char*)menuItemTmp->linkedVariable); - break; - case GEM_VAL_BOOLEAN: - if (*(boolean*)menuItemTmp->linkedVariable) { - _glcd.drawSprite(_menuValuesLeftOffset, yDraw, GEM_SPR_CHECKBOX_CHECKED, GLCD_MODE_NORMAL); - } else { - _glcd.drawSprite(_menuValuesLeftOffset, yDraw, GEM_SPR_CHECKBOX_UNCHECKED, GLCD_MODE_NORMAL); - } - break; - case GEM_VAL_SELECT: - { - GEMSelect* select = menuItemTmp->select; - printMenuItemValue(select->getSelectedOptionName(menuItemTmp->linkedVariable)); - _glcd.drawSprite(_glcd.xdim-7, yDraw, GEM_SPR_SELECT_ARROWS, GLCD_MODE_NORMAL); - } - break; - #ifdef GEM_SUPPORT_FLOAT_EDIT - case GEM_VAL_FLOAT: - // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards - dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); - printMenuItemValue(_valueString); - break; - case GEM_VAL_DOUBLE: - // sprintf(_valueString,"%.6f", *(double*)menuItemTmp->linkedVariable); // May work for non-AVR boards - dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); - printMenuItemValue(_valueString); - break; - #endif + { + _glcd.setX(5); + if (menuItemTmp->readonly) { + printMenuItemTitle(menuItemTmp->title, -1); + _glcd.putstr((char*)"^"); + } else { + printMenuItemTitle(menuItemTmp->title); + } + + byte menuValuesLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; + _glcd.setX(menuValuesLeftOffset); + switch (menuItemTmp->linkedType) { + case GEM_VAL_INTEGER: + itoa(*(int*)menuItemTmp->linkedVariable, _valueString, 10); + printMenuItemValue(_valueString); + break; + case GEM_VAL_BYTE: + itoa(*(byte*)menuItemTmp->linkedVariable, _valueString, 10); + printMenuItemValue(_valueString); + break; + case GEM_VAL_CHAR: + printMenuItemValue((char*)menuItemTmp->linkedVariable); + break; + case GEM_VAL_BOOL: + if (*(bool*)menuItemTmp->linkedVariable) { + _glcd.drawSprite(menuValuesLeftOffset, yDraw, GEM_SPR_CHECKBOX_CHECKED, GLCD_MODE_NORMAL); + } else { + _glcd.drawSprite(menuValuesLeftOffset, yDraw, GEM_SPR_CHECKBOX_UNCHECKED, GLCD_MODE_NORMAL); + } + break; + case GEM_VAL_SELECT: + { + GEMSelect* select = menuItemTmp->select; + printMenuItemValue(select->getSelectedOptionName(menuItemTmp->linkedVariable)); + _glcd.drawSprite(_glcd.xdim-7, yDraw, GEM_SPR_SELECT_ARROWS, GLCD_MODE_NORMAL); + } + break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + switch (spinner->getType()) { + case GEM_VAL_BYTE: + itoa(*(byte*)menuItemTmp->linkedVariable, _valueString, 10); + break; + case GEM_VAL_INTEGER: + itoa(*(int*)menuItemTmp->linkedVariable, _valueString, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + break; + case GEM_VAL_DOUBLE: + dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + break; + #endif + } + printMenuItemValue(_valueString); + _glcd.drawSprite(_glcd.xdim-7, yDraw, GEM_SPR_SELECT_ARROWS, GLCD_MODE_NORMAL); + } + break; + #endif + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards + dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + printMenuItemValue(_valueString); + break; + case GEM_VAL_DOUBLE: + // sprintf(_valueString,"%.6f", *(double*)menuItemTmp->linkedVariable); // May work for non-AVR boards + dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + printMenuItemValue(_valueString); + break; + #endif + } + break; } - break; case GEM_ITEM_LINK: _glcd.setX(5); if (menuItemTmp->readonly) { @@ -312,7 +411,6 @@ void GEM::printMenuItems() { _glcd.drawSprite(_glcd.xdim-8, yDraw, GEM_SPR_ARROW_RIGHT, GLCD_MODE_NORMAL); break; case GEM_ITEM_BACK: - _glcd.setX(11); _glcd.drawSprite(5, yDraw, GEM_SPR_ARROW_LEFT, GLCD_MODE_NORMAL); break; case GEM_ITEM_BUTTON: @@ -327,7 +425,7 @@ void GEM::printMenuItems() { break; } menuItemTmp = menuItemTmp->getMenuItemNext(); - y += _menuItemHeight; + y += getCurrentAppearance()->menuItemHeight; i++; } memset(_valueString, '\0', GEM_STR_LEN - 1); @@ -337,22 +435,23 @@ void GEM::drawMenuPointer() { if (_menuPageCurrent->itemsCount > 0) { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); int pointerPosition = getCurrentItemTopOffset(false); - if (_menuPointerType == GEM_POINTER_DASH) { - _glcd.eraseBox(0, _menuPageScreenTopOffset, 1, _glcd.ydim-1); + byte menuItemHeight = getCurrentAppearance()->menuItemHeight; + if (getCurrentAppearance()->menuPointerType == GEM_POINTER_DASH) { + _glcd.eraseBox(0, getCurrentAppearance()->menuPageScreenTopOffset, 1, _glcd.ydim-1); if (menuItemTmp->readonly) { - for (byte i = 0; i < (_menuItemHeight - 1) / 2; i++) { + for (byte i = 0; i < (menuItemHeight - 1) / 2; i++) { _glcd.drawPixel(0, pointerPosition + i * 2, GLCD_MODE_NORMAL); _glcd.drawPixel(1, pointerPosition + i * 2 + 1, GLCD_MODE_NORMAL); } } else { - _glcd.drawBox(0, pointerPosition, 1, pointerPosition + _menuItemHeight - 2, GLCD_MODE_NORMAL); + _glcd.drawBox(0, pointerPosition, 1, pointerPosition + menuItemHeight - 2, GLCD_MODE_NORMAL); } } else { _glcd.drawMode(GLCD_MODE_XOR); - _glcd.fillBox(0, pointerPosition-1, _glcd.xdim-3, pointerPosition + _menuItemHeight - 1); + _glcd.fillBox(0, pointerPosition-1, _glcd.xdim-3, pointerPosition + menuItemHeight - 1); _glcd.drawMode(GLCD_MODE_NORMAL); if (menuItemTmp->readonly) { - for (byte i = 0; i < (_menuItemHeight + 2) / 2; i++) { + for (byte i = 0; i < (menuItemHeight + 2) / 2; i++) { _glcd.drawPixel(0, pointerPosition + i * 2, GLCD_MODE_REVERSE); _glcd.drawPixel(1, pointerPosition + i * 2 - 1, GLCD_MODE_REVERSE); } @@ -362,11 +461,13 @@ void GEM::drawMenuPointer() { } void GEM::drawScrollbar() { - byte screensCount = (_menuPageCurrent->itemsCount % _menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / _menuItemsPerScreen : _menuPageCurrent->itemsCount / _menuItemsPerScreen + 1; + byte menuItemsPerScreen = getMenuItemsPerScreen(); + byte screensCount = (_menuPageCurrent->itemsCount % menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / menuItemsPerScreen : _menuPageCurrent->itemsCount / menuItemsPerScreen + 1; if (screensCount > 1) { - byte currentScreenNum = _menuPageCurrent->currentItemNum / _menuItemsPerScreen; - byte scrollbarHeight = (_glcd.ydim - _menuPageScreenTopOffset + 1) / screensCount; - byte scrollbarPosition = currentScreenNum * scrollbarHeight + _menuPageScreenTopOffset - 1; + byte currentScreenNum = _menuPageCurrent->currentItemNum / menuItemsPerScreen; + byte menuPageScreenTopOffset = getCurrentAppearance()->menuPageScreenTopOffset; + byte scrollbarHeight = (_glcd.ydim - menuPageScreenTopOffset + 1) / screensCount; + byte scrollbarPosition = currentScreenNum * scrollbarHeight + menuPageScreenTopOffset - 1; _glcd.drawLine(_glcd.xdim - 1, scrollbarPosition, _glcd.xdim - 1, scrollbarPosition + scrollbarHeight, GLCD_MODE_NORMAL); } } @@ -374,7 +475,7 @@ void GEM::drawScrollbar() { //====================== MENU ITEMS NAVIGATION void GEM::nextMenuItem() { - if (_menuPointerType != GEM_POINTER_DASH) { + if (getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) { drawMenuPointer(); } if (_menuPageCurrent->currentItemNum == _menuPageCurrent->itemsCount-1) { @@ -382,7 +483,8 @@ void GEM::nextMenuItem() { } else { _menuPageCurrent->currentItemNum++; } - boolean redrawMenu = (_menuPageCurrent->itemsCount > _menuItemsPerScreen && _menuPageCurrent->currentItemNum % _menuItemsPerScreen == 0); + byte menuItemsPerScreen = getMenuItemsPerScreen(); + bool redrawMenu = (_menuPageCurrent->itemsCount > menuItemsPerScreen && _menuPageCurrent->currentItemNum % menuItemsPerScreen == 0); if (redrawMenu) { drawMenu(); } else { @@ -391,10 +493,11 @@ void GEM::nextMenuItem() { } void GEM::prevMenuItem() { - if (_menuPointerType != GEM_POINTER_DASH) { + if (getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) { drawMenuPointer(); } - boolean redrawMenu = (_menuPageCurrent->itemsCount > _menuItemsPerScreen && _menuPageCurrent->currentItemNum % _menuItemsPerScreen == 0); + byte menuItemsPerScreen = getMenuItemsPerScreen(); + bool redrawMenu = (_menuPageCurrent->itemsCount > menuItemsPerScreen && _menuPageCurrent->currentItemNum % menuItemsPerScreen == 0); if (_menuPageCurrent->currentItemNum == 0) { _menuPageCurrent->currentItemNum = _menuPageCurrent->itemsCount-1; } else { @@ -444,7 +547,7 @@ void GEM::enterEditValueMode() { _editValueMode = true; GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - if (_menuPointerType != GEM_POINTER_DASH) { + if (getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) { drawMenuPointer(); } _editValueType = menuItemTmp->linkedType; @@ -464,7 +567,7 @@ void GEM::enterEditValueMode() { _editValueLength = GEM_STR_LEN - 1; initEditValueCursor(); break; - case GEM_VAL_BOOLEAN: + case GEM_VAL_BOOL: checkboxToggle(); break; case GEM_VAL_SELECT: @@ -474,6 +577,15 @@ void GEM::enterEditValueMode() { initEditValueCursor(); } break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + _valueSelectNum = spinner->getSelectedOptionNum(menuItemTmp->linkedVariable); + initEditValueCursor(); + } + break; + #endif #ifdef GEM_SUPPORT_FLOAT_EDIT case GEM_VAL_FLOAT: // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards @@ -494,8 +606,8 @@ void GEM::enterEditValueMode() { void GEM::checkboxToggle() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); int topOffset = getCurrentItemTopOffset(true, true); - boolean checkboxValue = *(boolean*)menuItemTmp->linkedVariable; - *(boolean*)menuItemTmp->linkedVariable = !checkboxValue; + bool checkboxValue = *(bool*)menuItemTmp->linkedVariable; + *(bool*)menuItemTmp->linkedVariable = !checkboxValue; if (menuItemTmp->callbackAction != nullptr) { if (menuItemTmp->callbackWithArgs) { menuItemTmp->callbackActionArg(menuItemTmp->callbackData); @@ -505,11 +617,11 @@ void GEM::checkboxToggle() { exitEditValue(); } else { if (!checkboxValue) { - _glcd.drawSprite(_menuValuesLeftOffset, topOffset, GEM_SPR_CHECKBOX_CHECKED, GLCD_MODE_NORMAL); + _glcd.drawSprite(getCurrentAppearance()->menuValuesLeftOffset, topOffset, GEM_SPR_CHECKBOX_CHECKED, GLCD_MODE_NORMAL); } else { - _glcd.drawSprite(_menuValuesLeftOffset, topOffset, GEM_SPR_CHECKBOX_UNCHECKED, GLCD_MODE_NORMAL); + _glcd.drawSprite(getCurrentAppearance()->menuValuesLeftOffset, topOffset, GEM_SPR_CHECKBOX_UNCHECKED, GLCD_MODE_NORMAL); } - if (_menuPointerType != GEM_POINTER_DASH) { + if (getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) { drawMenuPointer(); } _editValueMode = false; @@ -518,9 +630,9 @@ void GEM::checkboxToggle() { void GEM::clearValueVisibleRange() { int pointerPosition = getCurrentItemTopOffset(false); - byte cursorLeftOffset = _menuValuesLeftOffset; - _glcd.fillBox(cursorLeftOffset - 1, pointerPosition - 1, _glcd.xdim - 3, pointerPosition + _menuItemHeight - 1, 0x00); - _glcd.setX(_menuValuesLeftOffset); + byte menuValuesLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; + _glcd.fillBox(menuValuesLeftOffset - 1, pointerPosition - 1, _glcd.xdim - 3, pointerPosition + getCurrentAppearance()->menuItemHeight - 1, 0x00); + _glcd.setX(menuValuesLeftOffset); _glcd.setY(getCurrentItemTopOffset()); } @@ -532,12 +644,13 @@ void GEM::initEditValueCursor() { void GEM::nextEditValueCursorPosition() { drawEditValueCursor(); - if ((_editValueCursorPosition != _menuItemValueLength - 1) && (_editValueCursorPosition != _editValueLength - 1) && (_valueString[_editValueCursorPosition] != '\0')) { + byte menuItemValueLength = getMenuItemValueLength(); + if ((_editValueCursorPosition != menuItemValueLength - 1) && (_editValueCursorPosition != _editValueLength - 1) && (_valueString[_editValueCursorPosition] != '\0')) { _editValueCursorPosition++; } if ((_editValueVirtualCursorPosition != _editValueLength - 1) && (_valueString[_editValueVirtualCursorPosition] != '\0')) { _editValueVirtualCursorPosition++; - if (_editValueCursorPosition == _menuItemValueLength - 1) { + if (_editValueCursorPosition == menuItemValueLength - 1) { clearValueVisibleRange(); printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); } @@ -562,33 +675,58 @@ void GEM::prevEditValueCursorPosition() { void GEM::drawEditValueCursor() { int pointerPosition = getCurrentItemTopOffset(false); - byte cursorLeftOffset = _menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[_menuItemFontSize].width; + byte menuItemFontSize = getMenuItemFontSize(); + byte cursorLeftOffset = getCurrentAppearance()->menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[menuItemFontSize].width; _glcd.drawMode(GLCD_MODE_XOR); - if (_editValueType == GEM_VAL_SELECT) { - _glcd.fillBox(cursorLeftOffset - 1, pointerPosition - 1, _glcd.xdim - 3, pointerPosition + _menuItemHeight - 1); + if (_editValueType == GEM_VAL_SELECT || _editValueType == GEM_VAL_SPINNER) { + _glcd.fillBox(cursorLeftOffset - 1, pointerPosition - 1, _glcd.xdim - 3, pointerPosition + getCurrentAppearance()->menuItemHeight - 1); } else { - _glcd.fillBox(cursorLeftOffset - 1, pointerPosition - 1, cursorLeftOffset + _menuItemFont[_menuItemFontSize].width - 1, pointerPosition + _menuItemHeight - 1); + _glcd.fillBox(cursorLeftOffset - 1, pointerPosition - 1, cursorLeftOffset + _menuItemFont[menuItemFontSize].width - 1, pointerPosition + getCurrentAppearance()->menuItemHeight - 1); } _glcd.drawMode(GLCD_MODE_NORMAL); } void GEM::nextEditValueDigit() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); char chr = _valueString[_editValueVirtualCursorPosition]; byte code = (byte)chr; if (_editValueType == GEM_VAL_CHAR) { - switch (code) { - case 0: - code = GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_TILDA: - code = GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_LINE - 1: - code = GEM_CHAR_CODE_LINE + 1; - break; - default: - code++; - break; + if (menuItemTmp->adjustedAsciiOrder) { + switch (code) { + case 0: + code = GEM_CHAR_CODE_a; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_a; + break; + case GEM_CHAR_CODE_ACCENT: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_TILDA: + code = GEM_CHAR_CODE_BANG; + break; + case GEM_CHAR_CODE_LINE - 1: + code = GEM_CHAR_CODE_LINE + 1; + break; + default: + code++; + break; + } + } else { + switch (code) { + case 0: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_TILDA: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_LINE - 1: + code = GEM_CHAR_CODE_LINE + 1; + break; + default: + code++; + break; + } } } else { switch (code) { @@ -616,22 +754,46 @@ void GEM::nextEditValueDigit() { } void GEM::prevEditValueDigit() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); char chr = _valueString[_editValueVirtualCursorPosition]; byte code = (byte)chr; if (_editValueType == GEM_VAL_CHAR) { - switch (code) { - case 0: - code = GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_SPACE: - code = GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_LINE + 1: - code = GEM_CHAR_CODE_LINE - 1; - break; - default: - code--; - break; + if (menuItemTmp->adjustedAsciiOrder) { + switch (code) { + case 0: + code = GEM_CHAR_CODE_ACCENT; + break; + case GEM_CHAR_CODE_BANG: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_a: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_ACCENT; + break; + case GEM_CHAR_CODE_LINE + 1: + code = GEM_CHAR_CODE_LINE - 1; + break; + default: + code--; + break; + } + } else { + switch (code) { + case 0: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_LINE + 1: + code = GEM_CHAR_CODE_LINE - 1; + break; + default: + code--; + break; + } } } else { switch (code) { @@ -662,7 +824,7 @@ void GEM::drawEditValueDigit(byte code) { char chrNew = (char)code; _valueString[_editValueVirtualCursorPosition] = chrNew; drawEditValueCursor(); - _glcd.setX(_menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[_menuItemFontSize].width); + _glcd.setX(getCurrentAppearance()->menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[getMenuItemFontSize()].width); int pointerPosition = getCurrentItemTopOffset(); _glcd.setY(pointerPosition); _glcd.put(code); @@ -679,26 +841,72 @@ void GEM::nextEditValueSelect() { } void GEM::prevEditValueSelect() { - GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - GEMSelect* select = menuItemTmp->select; if (_valueSelectNum > 0) { _valueSelectNum--; } drawEditValueSelect(); } +#ifdef GEM_SUPPORT_SPINNER +void GEM::nextEditValueSpinner() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); + GEMSpinner* spinner = menuItemTmp->spinner; + if (_valueSelectNum+1 < spinner->getLength()) { + _valueSelectNum++; + } + drawEditValueSelect(); +} + +void GEM::prevEditValueSpinner() { + prevEditValueSelect(); +} +#endif + void GEM::drawEditValueSelect() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - GEMSelect* select = menuItemTmp->select; clearValueVisibleRange(); - printMenuItemValue(select->getOptionNameByIndex(_valueSelectNum)); + + switch (menuItemTmp->linkedType) { + case GEM_VAL_SELECT: + { + GEMSelect* select = menuItemTmp->select; + printMenuItemValue(select->getOptionNameByIndex(_valueSelectNum)); + } + break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + char valueStringTmp[GEM_STR_LEN]; + GEMSpinner* spinner = menuItemTmp->spinner; + GEMSpinnerValue valueTmp = spinner->getOptionNameByIndex(menuItemTmp->linkedVariable, _valueSelectNum); + switch (spinner->getType()) { + case GEM_VAL_BYTE: + itoa(valueTmp.valByte, valueStringTmp, 10); + break; + case GEM_VAL_INTEGER: + itoa(valueTmp.valInt, valueStringTmp, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(valueTmp.valFloat, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + case GEM_VAL_DOUBLE: + dtostrf(valueTmp.valDouble, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + #endif + } + printMenuItemValue(valueStringTmp); + } + break; + #endif + } + _glcd.drawSprite(_glcd.xdim - 7, getCurrentItemTopOffset(true, true), GEM_SPR_SELECT_ARROWS, GLCD_MODE_NORMAL); drawEditValueCursor(); } void GEM::saveEditValue() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - void* temp; switch (menuItemTmp->linkedType) { case GEM_VAL_INTEGER: *(int*)menuItemTmp->linkedVariable = atoi(_valueString); @@ -715,6 +923,14 @@ void GEM::saveEditValue() { select->setValue(menuItemTmp->linkedVariable, _valueSelectNum); } break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + spinner->setValue(menuItemTmp->linkedVariable, _valueSelectNum); + } + break; + #endif #ifdef GEM_SUPPORT_FLOAT_EDIT case GEM_VAL_FLOAT: *(float*)menuItemTmp->linkedVariable = atof(_valueString); @@ -746,6 +962,10 @@ void GEM::exitEditValue() { drawMenu(); } +bool GEM::isEditMode() { + return _editValueMode; +} + // Trim leading/trailing whitespaces // Author: Adam Rosenfield, https://stackoverflow.com/a/122721 char* GEM::trimString(char* str) { @@ -769,7 +989,7 @@ char* GEM::trimString(char* str) { //====================== KEY DETECTION -boolean GEM::readyForKey() { +bool GEM::readyForKey() { if ( (context.loop == nullptr) || ((context.loop != nullptr) && (context.allowExit)) ) { return true; @@ -780,9 +1000,10 @@ boolean GEM::readyForKey() { } -void GEM::registerKeyPress(byte keyCode) { +GEM& GEM::registerKeyPress(byte keyCode) { _currentKey = keyCode; dispatchKeyPress(); + return *this; } void GEM::dispatchKeyPress() { @@ -806,24 +1027,36 @@ void GEM::dispatchKeyPress() { case GEM_KEY_UP: if (_editValueType == GEM_VAL_SELECT) { prevEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + } else if (_editValueType == GEM_VAL_SPINNER) { + prevEditValueSpinner(); + #endif + } else if (_invertKeysDuringEdit) { + prevEditValueDigit(); } else { nextEditValueDigit(); } break; case GEM_KEY_RIGHT: - if (_editValueType != GEM_VAL_SELECT) { + if (_editValueType != GEM_VAL_SELECT && _editValueType != GEM_VAL_SPINNER) { nextEditValueCursorPosition(); } break; case GEM_KEY_DOWN: if (_editValueType == GEM_VAL_SELECT) { nextEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + } else if (_editValueType == GEM_VAL_SPINNER) { + nextEditValueSpinner(); + #endif + } else if (_invertKeysDuringEdit) { + nextEditValueDigit(); } else { prevEditValueDigit(); } break; case GEM_KEY_LEFT: - if (_editValueType != GEM_VAL_SELECT) { + if (_editValueType != GEM_VAL_SELECT && _editValueType != GEM_VAL_SPINNER) { prevEditValueCursorPosition(); } break; diff --git a/src/GEM.h b/src/GEM.h index 5a54e5e..b08ba16 100644 --- a/src/GEM.h +++ b/src/GEM.h @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -40,8 +40,13 @@ #ifdef GEM_ENABLE_GLCD_VERSION #include +#include "GEMAppearance.h" +#include "GEMContext.h" #include "GEMPage.h" #include "GEMSelect.h" +#ifdef GEM_SUPPORT_SPINNER +#include "GEMSpinner.h" +#endif #include "constants.h" // Macro constants (aliases) for the keys (buttons) used to navigate and interact with menu @@ -51,7 +56,7 @@ #define GEM_KEY_DOWN 3 // Down key is pressed (navigate down through the menu items list, select previous value of the digit/char of editable variable, or next option in select) #define GEM_KEY_LEFT 4 // Left key is pressed (navigate through the Back button to the previous menu page, select previous digit/char of editable variable) #define GEM_KEY_CANCEL 5 // Cancel key is pressed (navigate to the previous (parent) menu page, exit edit mode without saving the variable, exit context loop if allowed within context's settings) -#define GEM_KEY_OK 6 // Ok/Apply key is pressed (toggle boolean menu item, enter edit mode of the associated non-boolean variable, exit edit mode with saving the variable, execute code associated with button) +#define GEM_KEY_OK 6 // Ok/Apply key is pressed (toggle bool menu item, enter edit mode of the associated non-bool variable, exit edit mode with saving the variable, execute code associated with button) // Declaration of FontSize type struct FontSize { @@ -59,19 +64,6 @@ struct FontSize { byte height; // Height of the character }; -// Declaration of AppContext type -struct AppContext { - void (*loop)(); // Pointer to loop() function of current context (similar to regular loop() function: if context is defined, executed each regular loop() iteration), - // usually contains code of user-defined action that is run when menu Button is pressed - void (*enter)(); // Pointer to enter() function of current context (similar to regular setup() function, called manually, generally once before context's loop() function, optional), - // usually contains some additional set up required by the user-defined action pointed to by context's loop() - void (*exit)(); // Pointer to exit() function of current context (executed when user exits currently running context, optional), - // usually contains instructions to do some cleanup after context's loop() and to draw menu on screen again, - // if no user-defined function specified, default action will take place that consists of call to reInit(), drawMenu() and clearContext() methods - boolean allowExit = true; // Setting to false will require manually exit the context's loop() from within the loop itself (all necessary key detection should be done in context's loop() accordingly), - // otherwise exit is handled automatically by pressing GEM_KEY_CANCEL key (default is true) -}; - // Forward declaration of necessary classes class GEMItem; @@ -94,99 +86,120 @@ class GEM { default 86 (suitable for 128x64 screen with other variables at their default values) */ GEM(GLCD& glcd_, byte menuPointerType_ = GEM_POINTER_ROW, byte menuItemsPerScreen_ = 5, byte menuItemHeight_ = 10, byte menuPageScreenTopOffset_ = 10, byte menuValuesLeftOffset_ = 86); + /* + @param 'glcd_' - reference to the instance of the GLCD class created with AltSerialGraphicLCD library + @param 'appearance_' - object of type GEMAppearance + */ + GEM(GLCD& glcd_, GEMAppearance appearance_); + + /* APPEARANCE OPERATIONS */ + + GEM& setAppearance(GEMAppearance appearance); // Set apperance of the menu (can be overridden in GEMPage on per page basis) + GEMAppearance* getCurrentAppearance(); // Get appearance (as a pointer to GEMAppearance) applied to current menu page (or general if menu page has none of its own) /* INIT OPERATIONS */ - void setSplash(const uint8_t *sprite); // Set custom sprite displayed as the splash screen when GEM is being initialized. Should be called before GEM::init(). - // The following is the format of the sprite as described in AltSerialGraphicLCD library documentation. - // The sprite commences with two bytes which are the width and height of the image in pixels. - // The pixel data is organised as rows of 8 vertical pixels per byte where the least significant bit (LSB) - // is the top-left pixel and the most significant bit (MSB) tends towards the bottom-left pixel. - // A complete row of 8 vertical pixels across the image width comprises the first row, this is then followed - // by the next row of 8 vertical pixels and so on. - // Where the image height is not an exact multiple of 8 bits then any unused bits are typically set to zero - // (although this does not matter). - void setSplashDelay(uint16_t value); // Set splash screen delay. Default value 1000ms, max value 65535ms. Setting to 0 will disable splash screen. Should be called before GEM::init(). - void hideVersion(boolean flag = true); // Turn printing of the current GEM library version on splash screen off or back on. Should be called before GEM::init(). - void init(); // Init the menu (load necessary sprites into RAM of the SparkFun Graphic LCD Serial Backpack, display GEM splash screen, etc.) - void reInit(); // Reinitialize the menu (apply GEM specific settings to AltSerialGraphicLCD library) - void setMenuPageCurrent(GEMPage& menuPageCurrent); // Set supplied menu page as current + GEM& setSplash(const uint8_t *sprite); // Set custom sprite displayed as the splash screen when GEM is being initialized. Should be called before GEM::init(). + // The following is the format of the sprite as described in AltSerialGraphicLCD library documentation. + // The sprite commences with two bytes which are the width and height of the image in pixels. + // The pixel data is organised as rows of 8 vertical pixels per byte where the least significant bit (LSB) + // is the top-left pixel and the most significant bit (MSB) tends towards the bottom-left pixel. + // A complete row of 8 vertical pixels across the image width comprises the first row, this is then followed + // by the next row of 8 vertical pixels and so on. + // Where the image height is not an exact multiple of 8 bits then any unused bits are typically set to zero + // (although this does not matter). + GEM& setSplashDelay(uint16_t value); // Set splash screen delay. Default value 1000ms, max value 65535ms. Setting to 0 will disable splash screen. Should be called before GEM::init(). + GEM& hideVersion(bool flag = true); // Turn printing of the current GEM library version on splash screen off or back on. Should be called before GEM::init(). + GEM& invertKeysDuringEdit(bool invert = true); // Turn inverted order of characters during edit mode on or off + GEM_VIRTUAL GEM& init(); // Init the menu (load necessary sprites into RAM of the SparkFun Graphic LCD Serial Backpack, display GEM splash screen, etc.) + GEM_VIRTUAL GEM& reInit(); // Reinitialize the menu (apply GEM specific settings to AltSerialGraphicLCD library) + GEM& setMenuPageCurrent(GEMPage& menuPageCurrent); // Set supplied menu page as current + GEMPage* getCurrentMenuPage(); // Get pointer to current menu page /* CONTEXT OPERATIONS */ - AppContext context; // Currently set context - void clearContext(); // Clear context + GEMContext context; // Currently set context + GEM& clearContext(); // Clear context /* DRAW OPERATIONS */ - void drawMenu(); // Draw menu on screen, with menu page set earlier in GEM::setMenuPageCurrent() + GEM_VIRTUAL GEM& drawMenu(); // Draw menu on screen, with menu page set earlier in GEM::setMenuPageCurrent() + GEM& setDrawMenuCallback(void (*drawMenuCallback_)()); // Set callback that will be called at the end of GEM::drawMenu() + GEM& removeDrawMenuCallback(); // Remove callback that was called at the end of GEM::drawMenu() + + /* VALUE EDIT */ + + bool isEditMode(); // Checks if menu is in edit mode /* KEY DETECTION */ - boolean readyForKey(); // Check that menu is waiting for the key press - void registerKeyPress(byte keyCode); // Register the key press and trigger corresponding action - // Accepts GEM_KEY_NONE, GEM_KEY_UP, GEM_KEY_RIGHT, GEM_KEY_DOWN, GEM_KEY_LEFT, GEM_KEY_CANCEL, GEM_KEY_OK values - private: + bool readyForKey(); // Checks that menu is waiting for the key press + GEM& registerKeyPress(byte keyCode); // Register the key press and trigger corresponding action + // Accepts GEM_KEY_NONE, GEM_KEY_UP, GEM_KEY_RIGHT, GEM_KEY_DOWN, GEM_KEY_LEFT, GEM_KEY_CANCEL, GEM_KEY_OK values + protected: GLCD& _glcd; - byte _menuPointerType; - byte _menuItemsPerScreen; - byte _menuItemHeight; - byte _menuPageScreenTopOffset; - byte _menuValuesLeftOffset; - byte _menuItemFontSize; + GEMAppearance* _appearanceCurrent = nullptr; + GEMAppearance _appearance; + byte getMenuItemsPerScreen(); + byte getMenuItemFontSize(); FontSize _menuItemFont[2] = {{6,8},{4,6}}; - byte _menuItemInsetOffset; - byte _menuItemTitleLength; - byte _menuItemValueLength; + bool _invertKeysDuringEdit = false; + GEM_VIRTUAL byte getMenuItemTitleLength(); + GEM_VIRTUAL byte getMenuItemValueLength(); const uint8_t *_splash; uint16_t _splashDelay = 1000; - boolean _enableVersion = true; + bool _enableVersion = true; /* DRAW OPERATIONS */ GEMPage* _menuPageCurrent = nullptr; - void drawTitleBar(); - void printMenuItemString(const char* str, byte num, byte startPos = 0); - void printMenuItemTitle(const char* str, int offset = 0); - void printMenuItemValue(const char* str, int offset = 0, byte startPos = 0); - void printMenuItemFull(const char* str, int offset = 0); - byte getMenuItemInsetOffset(boolean forSprite = false); - byte getCurrentItemTopOffset(boolean withInsetOffset = true, boolean forSprite = false); - void printMenuItems(); - void drawMenuPointer(); - void drawScrollbar(); + void (*drawMenuCallback)() = nullptr; + GEM_VIRTUAL void drawTitleBar(); + GEM_VIRTUAL void printMenuItemString(const char* str, byte num, byte startPos = 0); + GEM_VIRTUAL void printMenuItemTitle(const char* str, int offset = 0); + GEM_VIRTUAL void printMenuItemValue(const char* str, int offset = 0, byte startPos = 0); + GEM_VIRTUAL void printMenuItemFull(const char* str, int offset = 0); + GEM_VIRTUAL byte getMenuItemInsetOffset(bool forSprite = false); + GEM_VIRTUAL byte getCurrentItemTopOffset(bool withInsetOffset = true, bool forSprite = false); + GEM_VIRTUAL void printMenuItems(); + GEM_VIRTUAL void drawMenuPointer(); + GEM_VIRTUAL void drawScrollbar(); /* MENU ITEMS NAVIGATION */ - void nextMenuItem(); - void prevMenuItem(); - void menuItemSelect(); + GEM_VIRTUAL void nextMenuItem(); + GEM_VIRTUAL void prevMenuItem(); + GEM_VIRTUAL void menuItemSelect(); /* VALUE EDIT */ - boolean _editValueMode; + bool _editValueMode; byte _editValueType; byte _editValueLength; byte _editValueCursorPosition; byte _editValueVirtualCursorPosition; char _valueString[GEM_STR_LEN]; int _valueSelectNum; - void enterEditValueMode(); - void checkboxToggle(); - void clearValueVisibleRange(); - void initEditValueCursor(); - void nextEditValueCursorPosition(); - void prevEditValueCursorPosition(); - void drawEditValueCursor(); - void nextEditValueDigit(); - void prevEditValueDigit(); - void drawEditValueDigit(byte code); - void nextEditValueSelect(); - void prevEditValueSelect(); - void drawEditValueSelect(); - void saveEditValue(); - void cancelEditValue(); - void exitEditValue(); + GEM_VIRTUAL void enterEditValueMode(); + GEM_VIRTUAL void checkboxToggle(); + GEM_VIRTUAL void clearValueVisibleRange(); + GEM_VIRTUAL void initEditValueCursor(); + GEM_VIRTUAL void nextEditValueCursorPosition(); + GEM_VIRTUAL void prevEditValueCursorPosition(); + GEM_VIRTUAL void drawEditValueCursor(); + GEM_VIRTUAL void nextEditValueDigit(); + GEM_VIRTUAL void prevEditValueDigit(); + GEM_VIRTUAL void drawEditValueDigit(byte code); + GEM_VIRTUAL void nextEditValueSelect(); + GEM_VIRTUAL void prevEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + GEM_VIRTUAL void nextEditValueSpinner(); + GEM_VIRTUAL void prevEditValueSpinner(); + #endif + GEM_VIRTUAL void drawEditValueSelect(); + GEM_VIRTUAL void saveEditValue(); + GEM_VIRTUAL void cancelEditValue(); + GEM_VIRTUAL void exitEditValue(); char* trimString(char* str); /* KEY DETECTION */ diff --git a/src/GEMAppearance.h b/src/GEMAppearance.h new file mode 100644 index 0000000..9241562 --- /dev/null +++ b/src/GEMAppearance.h @@ -0,0 +1,49 @@ +/* + GEMAppearance - struct for storing visual settings of GEM library. + + GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) + and option selects. User-defined callback function can be specified to invoke when menu item is saved. + + Supports buttons that can invoke user-defined actions and create action-specific + context, which can have its own enter (setup) and exit callbacks as well as loop function. + + Supports: + - AltSerialGraphicLCD library by Jon Green (http://www.jasspa.com/serialGLCD.html); + - U8g2 library by olikraus (https://github.com/olikraus/U8g2_Arduino); + - Adafruit GFX library by Adafruit (https://github.com/adafruit/Adafruit-GFX-Library). + + For documentation visit: + https://github.com/Spirik/GEM + + Copyright (c) 2018-2023 Alexander 'Spirik' Spiridonov + + This file is part of GEM library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see . +*/ + +#ifndef HEADER_GEMAPPEARANCE +#define HEADER_GEMAPPEARANCE + +// Declaration of GEMAppearance type +struct GEMAppearance { + byte menuPointerType; + byte menuItemsPerScreen; + byte menuItemHeight; + byte menuPageScreenTopOffset; + byte menuValuesLeftOffset; +}; + +#endif diff --git a/src/GEMContext.h b/src/GEMContext.h new file mode 100644 index 0000000..3678370 --- /dev/null +++ b/src/GEMContext.h @@ -0,0 +1,55 @@ +/* + GEMContext (a.k.a. AppContext) - struct for storing "context" of currently executing user action. + + GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) + and option selects. User-defined callback function can be specified to invoke when menu item is saved. + + Supports buttons that can invoke user-defined actions and create action-specific + context, which can have its own enter (setup) and exit callbacks as well as loop function. + + Supports: + - AltSerialGraphicLCD library by Jon Green (http://www.jasspa.com/serialGLCD.html); + - U8g2 library by olikraus (https://github.com/olikraus/U8g2_Arduino); + - Adafruit GFX library by Adafruit (https://github.com/adafruit/Adafruit-GFX-Library). + + For documentation visit: + https://github.com/Spirik/GEM + + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov + + This file is part of GEM library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see . +*/ + +#ifndef HEADER_GEMCONTEXT +#define HEADER_GEMCONTEXT + +// Declaration of GEMContext type +struct GEMContext { + void (*loop)(); // Pointer to loop() function of current context (similar to regular loop() function: if context is defined, executed each regular loop() iteration), + // usually contains code of user-defined action that is run when menu Button is pressed + void (*enter)(); // Pointer to enter() function of current context (similar to regular setup() function, called manually, generally once before context's loop() function, optional), + // usually contains some additional set up required by the user-defined action pointed to by context's loop() + void (*exit)(); // Pointer to exit() function of current context (executed when user exits currently running context, optional), + // usually contains instructions to do some cleanup after context's loop() and to draw menu on screen again, + // if no user-defined function specified, default action will take place that consists of call to reInit(), drawMenu() and clearContext() methods + bool allowExit = true; // Setting to false will require manually exit the context's loop() from within the loop itself (all necessary key detection should be done in context's loop() accordingly), + // otherwise exit is handled automatically by pressing GEM_KEY_CANCEL key (default is true) +}; + +#define AppContext GEMContext + +#endif diff --git a/src/GEMItem.cpp b/src/GEMItem.cpp index 6c6dd34..76b37e8 100644 --- a/src/GEMItem.cpp +++ b/src/GEMItem.cpp @@ -2,7 +2,7 @@ GEMItem - menu item for GEM library. GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -16,7 +16,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -93,7 +93,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -104,7 +104,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -115,7 +115,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -126,7 +126,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -137,10 +137,10 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -148,7 +148,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -159,7 +159,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -170,7 +170,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -183,7 +183,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -194,7 +194,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -205,7 +205,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -216,7 +216,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -227,10 +227,10 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -238,7 +238,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -249,7 +249,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -260,7 +260,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, v , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -273,7 +273,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -284,7 +284,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -295,7 +295,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -306,7 +306,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -317,10 +317,10 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -328,7 +328,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -339,7 +339,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -350,7 +350,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -363,7 +363,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -374,7 +374,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -385,7 +385,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -396,7 +396,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -407,10 +407,10 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -418,7 +418,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -429,7 +429,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -440,7 +440,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -453,7 +453,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -464,7 +464,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -475,7 +475,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -486,7 +486,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -497,10 +497,10 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -508,7 +508,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -519,7 +519,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -530,12 +530,12 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ , select(&select_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- -GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, boolean readonly_) +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -544,7 +544,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, , select(&select_) { } -GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, boolean readonly_) +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -553,7 +553,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, b , select(&select_) { } -GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, boolean readonly_) +GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, bool readonly_) : title(title_) , linkedVariable(linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -562,7 +562,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, , select(&select_) { } -GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, boolean readonly_) +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -571,7 +571,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, , select(&select_) { } -GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, boolean readonly_) +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_SELECT) @@ -582,6 +582,444 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_ //--- +#ifdef GEM_SUPPORT_SPINNER +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackAction(callbackAction_) +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackAction(callbackAction_) +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackAction(callbackAction_) +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackAction(callbackAction_) +{ } + +//--- + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { 0 } } +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valByte = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valInt = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valFloat = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valDouble = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valBool = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valChar = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valPointer = callbackVal_ }} +{ } + +//--- + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { 0 } } +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valByte = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valInt = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valFloat = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valDouble = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valBool = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valChar = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valPointer = callbackVal_ }} +{ } + +//--- + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { 0 } } +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valByte = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valInt = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valFloat = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valDouble = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valBool = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valChar = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valPointer = callbackVal_ }} +{ } + +//--- + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { 0 } } +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valByte = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valInt = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valFloat = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valDouble = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valBool = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valChar = callbackVal_ }} +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , spinner(&spinner_) + , callbackActionArg(callbackAction_) + , callbackWithArgs(true) + , callbackData{ this, { .valPointer = callbackVal_ }} +{ } + +//--- + +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, bool readonly_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , readonly(readonly_) + , spinner(&spinner_) +{ } + +GEMItem::GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, bool readonly_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , readonly(readonly_) + , spinner(&spinner_) +{ } + +GEMItem::GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, bool readonly_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , readonly(readonly_) + , spinner(&spinner_) +{ } + +GEMItem::GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, bool readonly_) + : title(title_) + , linkedVariable(&linkedVariable_) + , linkedType(GEM_VAL_SPINNER) + , type(GEM_ITEM_VAL) + , readonly(readonly_) + , spinner(&spinner_) +{ } +#endif + +//--- + GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)()) : title(title_) , linkedVariable(&linkedVariable_) @@ -606,10 +1044,10 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , callbackAction(callbackAction_) { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)()) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)()) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackAction(callbackAction_) { } @@ -641,7 +1079,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -651,7 +1089,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -661,7 +1099,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -671,7 +1109,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -681,17 +1119,17 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_BYTE) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -701,7 +1139,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -711,7 +1149,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -723,7 +1161,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -733,7 +1171,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -743,7 +1181,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -753,7 +1191,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -763,17 +1201,17 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_INTEGER) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -783,7 +1221,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -793,7 +1231,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -805,7 +1243,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -815,7 +1253,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -825,7 +1263,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -835,7 +1273,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -845,17 +1283,17 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_CHAR) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -865,7 +1303,7 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -875,89 +1313,89 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, void (*callbackActio , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData)) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData)) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -969,7 +1407,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -979,7 +1417,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -989,7 +1427,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -999,7 +1437,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -1009,17 +1447,17 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_FLOAT) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -1029,7 +1467,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -1039,7 +1477,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, void (*callbackActi , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- @@ -1051,7 +1489,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_) @@ -1061,7 +1499,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_) @@ -1071,7 +1509,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_) @@ -1081,7 +1519,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_) @@ -1091,17 +1529,17 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_) +GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_DOUBLE) , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_) @@ -1111,7 +1549,7 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_) @@ -1121,12 +1559,12 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, void (*callbackAct , type(GEM_ITEM_VAL) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- -GEMItem::GEMItem(const char* title_, byte& linkedVariable_, boolean readonly_) +GEMItem::GEMItem(const char* title_, byte& linkedVariable_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_BYTE) @@ -1134,7 +1572,7 @@ GEMItem::GEMItem(const char* title_, byte& linkedVariable_, boolean readonly_) , readonly(readonly_) { } -GEMItem::GEMItem(const char* title_, int& linkedVariable_, boolean readonly_) +GEMItem::GEMItem(const char* title_, int& linkedVariable_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_INTEGER) @@ -1142,7 +1580,7 @@ GEMItem::GEMItem(const char* title_, int& linkedVariable_, boolean readonly_) , readonly(readonly_) { } -GEMItem::GEMItem(const char* title_, char* linkedVariable_, boolean readonly_) +GEMItem::GEMItem(const char* title_, char* linkedVariable_, bool readonly_) : title(title_) , linkedVariable(linkedVariable_) , linkedType(GEM_VAL_CHAR) @@ -1150,15 +1588,15 @@ GEMItem::GEMItem(const char* title_, char* linkedVariable_, boolean readonly_) , readonly(readonly_) { } -GEMItem::GEMItem(const char* title_, boolean& linkedVariable_, boolean readonly_) +GEMItem::GEMItem(const char* title_, bool& linkedVariable_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) - , linkedType(GEM_VAL_BOOLEAN) + , linkedType(GEM_VAL_BOOL) , type(GEM_ITEM_VAL) , readonly(readonly_) { } -GEMItem::GEMItem(const char* title_, float& linkedVariable_, boolean readonly_) +GEMItem::GEMItem(const char* title_, float& linkedVariable_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_FLOAT) @@ -1167,7 +1605,7 @@ GEMItem::GEMItem(const char* title_, float& linkedVariable_, boolean readonly_) , readonly(readonly_) { } -GEMItem::GEMItem(const char* title_, double& linkedVariable_, boolean readonly_) +GEMItem::GEMItem(const char* title_, double& linkedVariable_, bool readonly_) : title(title_) , linkedVariable(&linkedVariable_) , linkedType(GEM_VAL_DOUBLE) @@ -1178,21 +1616,21 @@ GEMItem::GEMItem(const char* title_, double& linkedVariable_, boolean readonly_) //--- -GEMItem::GEMItem(const char* title_, GEMPage& linkedPage_, boolean readonly_) +GEMItem::GEMItem(const char* title_, GEMPage& linkedPage_, bool readonly_) : title(title_) , type(GEM_ITEM_LINK) , readonly(readonly_) , linkedPage(&linkedPage_) { } -GEMItem::GEMItem(const char* title_, GEMPage* linkedPage_, boolean readonly_) +GEMItem::GEMItem(const char* title_, GEMPage* linkedPage_, bool readonly_) : title(title_) , type(GEM_ITEM_LINK) , readonly(readonly_) , linkedPage(linkedPage_) { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(), boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(), bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) @@ -1204,100 +1642,114 @@ GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData)) , type(GEM_ITEM_BUTTON) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this } + , callbackData{ this, { 0 } } { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valByte = callbackVal_ }} + , callbackData{ this, { .valByte = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), int callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), int callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valInt = callbackVal_ }} + , callbackData{ this, { .valInt = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), float callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), float callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valFloat = callbackVal_ }} + , callbackData{ this, { .valFloat = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), double callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), double callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valDouble = callbackVal_ }} + , callbackData{ this, { .valDouble = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valBool = callbackVal_ }} + , callbackData{ this, { .valBool = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valChar = callbackVal_ }} + , callbackData{ this, { .valChar = callbackVal_ }} { } -GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_, boolean readonly_) +GEMItem::GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_, bool readonly_) : title(title_) , type(GEM_ITEM_BUTTON) , readonly(readonly_) , callbackActionArg(callbackAction_) , callbackWithArgs(true) - , callbackData{ .pMenuItem = this, { .valPointer = callbackVal_ }} + , callbackData{ this, { .valPointer = callbackVal_ }} { } //--- -void GEMItem::setCallbackVal(byte callbackVal_) { +GEMItem& GEMItem::setCallbackVal(byte callbackVal_) { + callbackData.pMenuItem = this; callbackData.valByte = callbackVal_; + return *this; } -void GEMItem::setCallbackVal(int callbackVal_) { +GEMItem& GEMItem::setCallbackVal(int callbackVal_) { + callbackData.pMenuItem = this; callbackData.valInt = callbackVal_; + return *this; } -void GEMItem::setCallbackVal(float callbackVal_) { +GEMItem& GEMItem::setCallbackVal(float callbackVal_) { + callbackData.pMenuItem = this; callbackData.valFloat = callbackVal_; + return *this; } -void GEMItem::setCallbackVal(double callbackVal_) { +GEMItem& GEMItem::setCallbackVal(double callbackVal_) { + callbackData.pMenuItem = this; callbackData.valDouble = callbackVal_; + return *this; } -void GEMItem::setCallbackVal(boolean callbackVal_) { +GEMItem& GEMItem::setCallbackVal(bool callbackVal_) { + callbackData.pMenuItem = this; callbackData.valBool = callbackVal_; + return *this; } -void GEMItem::setCallbackVal(const char* callbackVal_) { +GEMItem& GEMItem::setCallbackVal(const char* callbackVal_) { + callbackData.pMenuItem = this; callbackData.valChar = callbackVal_; + return *this; } -void GEMItem::setCallbackVal(void* callbackVal_) { +GEMItem& GEMItem::setCallbackVal(void* callbackVal_) { + callbackData.pMenuItem = this; callbackData.valPointer = callbackVal_; + return *this; } GEMCallbackData GEMItem::getCallbackData() { @@ -1306,27 +1758,35 @@ GEMCallbackData GEMItem::getCallbackData() { //--- -void GEMItem::setTitle(const char* title_) { +GEMItem& GEMItem::setTitle(const char* title_) { title = title_; + return *this; } const char* GEMItem::getTitle() { return title; } -void GEMItem::setPrecision(byte prec) { +GEMItem& GEMItem::setPrecision(byte prec) { precision = prec; + return *this; } -void GEMItem::setReadonly(boolean mode) { +GEMItem& GEMItem::setAdjustedASCIIOrder(bool mode) { + adjustedAsciiOrder = mode; + return *this; +} + +GEMItem& GEMItem::setReadonly(bool mode) { readonly = mode; + return *this; } -boolean GEMItem::getReadonly() { +bool GEMItem::getReadonly() { return readonly; } -void GEMItem::hide(boolean hide) { +GEMItem& GEMItem::hide(bool hide) { if (hide) { if (!hidden) { if (parentPage != nullptr) { @@ -1338,9 +1798,10 @@ void GEMItem::hide(boolean hide) { } else { show(); } + return *this; } -void GEMItem::show() { +GEMItem& GEMItem::show() { if (hidden) { if (parentPage != nullptr) { parentPage->showMenuItem(*this); @@ -1348,19 +1809,27 @@ void GEMItem::show() { hidden = false; } } + return *this; } -boolean GEMItem::getHidden() { +bool GEMItem::getHidden() { return hidden; } +GEMItem& GEMItem::remove() { + if (parentPage != nullptr) { + parentPage->removeMenuItem(*this); + } + return *this; +} + void* GEMItem::getLinkedVariablePointer() { return linkedVariable; } -GEMItem* GEMItem::getMenuItemNext() { +GEMItem* GEMItem::getMenuItemNext(bool total) { GEMItem* menuItemTmp = menuItemNext; - while (menuItemTmp != nullptr && menuItemTmp->hidden) { + while (menuItemTmp != nullptr && !total && menuItemTmp->hidden) { menuItemTmp = menuItemTmp->menuItemNext; } return menuItemTmp; diff --git a/src/GEMItem.h b/src/GEMItem.h index 2ac7f1c..70a457b 100644 --- a/src/GEMItem.h +++ b/src/GEMItem.h @@ -2,7 +2,7 @@ GEMItem - menu item for GEM library. GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -16,7 +16,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -34,6 +34,7 @@ along with this library. If not, see . */ +#include "config.h" #include "constants.h" #include "GEMPage.h" @@ -56,6 +57,9 @@ class GEMItem; class GEMPage; class GEMSelect; +#ifdef GEM_SUPPORT_SPINNER +class GEMSpinner; +#endif // Declaration of GEMCallbackData type struct GEMCallbackData { @@ -65,7 +69,7 @@ struct GEMCallbackData { int valInt; float valFloat; double valDouble; - boolean valBoolean; + bool valBoolean; bool valBool; const char* valChar; void* valPointer; @@ -85,7 +89,7 @@ class GEMItem { @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, char*, float, or double) @param 'select_' - reference to GEMSelect option select @param 'callbackAction_' - pointer to callback function executed when associated variable is successfully saved - @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, boolean, float, double, char or void pointer) + @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, bool, float, double, char or void pointer) */ GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)()); GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)()); @@ -98,7 +102,7 @@ class GEMItem { GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -107,7 +111,7 @@ class GEMItem { GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -116,7 +120,7 @@ class GEMItem { GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -125,7 +129,7 @@ class GEMItem { GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -134,7 +138,7 @@ class GEMItem { GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); /* @@ -146,22 +150,85 @@ class GEMItem { values GEM_READONLY (alias for true) default false */ - GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, boolean readonly_ = false); - GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, boolean readonly_ = false); - GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, boolean readonly_ = false); - GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, boolean readonly_ = false); - GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, boolean readonly_ = false); + GEMItem(const char* title_, byte& linkedVariable_, GEMSelect& select_, bool readonly_ = false); + GEMItem(const char* title_, int& linkedVariable_, GEMSelect& select_, bool readonly_ = false); + GEMItem(const char* title_, char* linkedVariable_, GEMSelect& select_, bool readonly_ = false); + GEMItem(const char* title_, float& linkedVariable_, GEMSelect& select_, bool readonly_ = false); + GEMItem(const char* title_, double& linkedVariable_, GEMSelect& select_, bool readonly_ = false); + #ifdef GEM_SUPPORT_SPINNER + /* + Constructors for menu item that represents spinner, w/ callback (optionally w/ user-defined callback argument) + @param 'title_' - title of the menu item displayed on the screen + @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, float, or double) + @param 'spinner_' - reference to GEMSpinner object + @param 'callbackAction_' - pointer to callback function executed when associated variable is successfully saved + @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, bool, float, double, char or void pointer) + */ + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)()); + + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); + + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); + + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); + + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData)); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); + /* + Constructors for menu item that represents spinner, w/o callback + @param 'title_' - title of the menu item displayed on the screen + @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, float, or double) + @param 'spinner_' - reference to GEMSpinner object + @param 'readonly_' (optional) - set readonly mode for variable that spinner is associated with + values GEM_READONLY (alias for true) + default false + */ + GEMItem(const char* title_, byte& linkedVariable_, GEMSpinner& spinner_, bool readonly_ = false); + GEMItem(const char* title_, int& linkedVariable_, GEMSpinner& spinner_, bool readonly_ = false); + GEMItem(const char* title_, float& linkedVariable_, GEMSpinner& spinner_, bool readonly_ = false); + GEMItem(const char* title_, double& linkedVariable_, GEMSpinner& spinner_, bool readonly_ = false); + #endif /* Constructors for menu item that represents variable, w/ callback (optionally w/ user-defined callback argument) @param 'title_' - title of the menu item displayed on the screen - @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, char*, boolean, float, or double) + @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, char*, bool, float, or double) @param 'callbackAction_' - pointer to callback function executed when associated variable is successfully saved - @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, boolean, float, double, char or void pointer) + @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, bool, float, double, char or void pointer) */ GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)()); GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)()); GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)()); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)()); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)()); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)()); GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)()); @@ -170,7 +237,7 @@ class GEMItem { GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, byte& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -179,7 +246,7 @@ class GEMItem { GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, int& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -188,25 +255,25 @@ class GEMItem { GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, char* linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData)); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); - GEMItem(const char* title_, boolean& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData)); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); + GEMItem(const char* title_, bool& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData)); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, float& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); @@ -215,23 +282,23 @@ class GEMItem { GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), int callbackVal_); GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), float callbackVal_); GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), double callbackVal_); - GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_); + GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_); GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_); GEMItem(const char* title_, double& linkedVariable_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_); /* Constructors for menu item that represents variable, w/o callback @param 'title_' - title of the menu item displayed on the screen - @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, char*, boolean, float, or double) + @param 'linkedVariable_' - reference to variable that menu item is associated with (either byte, int, char*, bool, float, or double) @param 'readonly_' (optional) - set readonly mode for variable that menu item is associated with values GEM_READONLY (alias for true) default false */ - GEMItem(const char* title_, byte& linkedVariable_, boolean readonly_ = false); - GEMItem(const char* title_, int& linkedVariable_, boolean readonly_ = false); - GEMItem(const char* title_, char* linkedVariable_, boolean readonly_ = false); - GEMItem(const char* title_, boolean& linkedVariable_, boolean readonly_ = false); - GEMItem(const char* title_, float& linkedVariable_, boolean readonly_ = false); - GEMItem(const char* title_, double& linkedVariable_, boolean readonly_ = false); + GEMItem(const char* title_, byte& linkedVariable_, bool readonly_ = false); + GEMItem(const char* title_, int& linkedVariable_, bool readonly_ = false); + GEMItem(const char* title_, char* linkedVariable_, bool readonly_ = false); + GEMItem(const char* title_, bool& linkedVariable_, bool readonly_ = false); + GEMItem(const char* title_, float& linkedVariable_, bool readonly_ = false); + GEMItem(const char* title_, double& linkedVariable_, bool readonly_ = false); /* Constructor for menu item that represents link to another menu page (via reference) @param 'title_' - title of the menu item displayed on the screen @@ -239,7 +306,7 @@ class GEMItem { @param 'readonly_' (optional) - set readonly mode for the link (user won't be able to navigate to linked page) values GEM_READONLY (alias for true) */ - GEMItem(const char* title_, GEMPage& linkedPage_, boolean readonly_ = false); + GEMItem(const char* title_, GEMPage& linkedPage_, bool readonly_ = false); /* Constructor for menu item that represents link to another menu page (via pointer) @param 'title_' - title of the menu item displayed on the screen @@ -247,65 +314,71 @@ class GEMItem { @param 'readonly_' (optional) - set readonly mode for the link (user won't be able to navigate to linked page) values GEM_READONLY (alias for true) */ - GEMItem(const char* title_, GEMPage* linkedPage_, boolean readonly_ = false); + GEMItem(const char* title_, GEMPage* linkedPage_, bool readonly_ = false); /* Constructor for menu item that represents button w/ callback (optionally w/ user-defined callback argument) @param 'title_' - title of the menu item displayed on the screen @param 'callbackAction_' - pointer to function that will be executed when menu item is activated - @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, boolean, float, double, char or void pointer) + @param 'callbackVal_' - value of an argument that will be passed to callback within GEMCallbackData (either byte, int, bool, float, double, char or void pointer) @param 'readonly_' (optional) - set readonly mode for the button (user won't be able to call action associated with it) values GEM_READONLY (alias for true) */ - GEMItem(const char* title_, void (*callbackAction_)(), boolean readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(), bool readonly_ = false); GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData)); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_, boolean readonly_ = false); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), int callbackVal_, boolean readonly_ = false); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), float callbackVal_, boolean readonly_ = false); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), double callbackVal_, boolean readonly_ = false); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), boolean callbackVal_, boolean readonly_ = false); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_, boolean readonly_ = false); - GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_, boolean readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), byte callbackVal_, bool readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), int callbackVal_, bool readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), float callbackVal_, bool readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), double callbackVal_, bool readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), bool callbackVal_, bool readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), const char* callbackVal_, bool readonly_ = false); + GEMItem(const char* title_, void (*callbackAction_)(GEMCallbackData), void* callbackVal_, bool readonly_ = false); - void setCallbackVal(byte callbackVal_); // Set value of an argument that will be passed to callback within GEMCallbackData (either byte, int, boolean, float, double, char or void pointer) - void setCallbackVal(int callbackVal_); - void setCallbackVal(float callbackVal_); - void setCallbackVal(double callbackVal_); - void setCallbackVal(boolean callbackVal_); - void setCallbackVal(const char* callbackVal_); - void setCallbackVal(void* callbackVal_); - GEMCallbackData getCallbackData(); // Get GEMCallbackData struct associated with menu item - void setTitle(const char* title_); // Set title of the menu item - const char* getTitle(); // Get title of the menu item - void setPrecision(byte prec); // Explicitly set precision for float or double variables as required by dtostrf() conversion, - // i.e. the number of digits after the decimal sign - void setReadonly(boolean mode = true); // Explicitly set or unset readonly mode for variable that menu item is associated with - // (relevant for GEM_VAL_INTEGER, GEM_VAL_BYTE, GEM_VAL_FLOAT, GEM_VAL_DOUBLE, GEM_VAL_CHAR, - // GEM_VAL_BOOLEAN variable menu items and GEM_VAL_SELECT option select), or menu button GEM_ITEM_BUTTON - // and menu link GEM_ITEM_LINK, pressing of which won't result in any action, associated with them - boolean getReadonly(); // Get readonly state of the variable that menu item is associated with (as well as menu link or button) - void hide(boolean hide = true); // Explicitly hide or show menu item - void show(); // Explicitly show menu item - boolean getHidden(); // Get hidden state of the menu item - void* getLinkedVariablePointer(); // Get pointer to a linked variable (relevant for menu items that represent variable) - private: + GEMItem& setCallbackVal(byte callbackVal_); // Set value of an argument that will be passed to callback within GEMCallbackData (either byte, int, bool, float, double, char or void pointer) + GEMItem& setCallbackVal(int callbackVal_); + GEMItem& setCallbackVal(float callbackVal_); + GEMItem& setCallbackVal(double callbackVal_); + GEMItem& setCallbackVal(bool callbackVal_); + GEMItem& setCallbackVal(const char* callbackVal_); + GEMItem& setCallbackVal(void* callbackVal_); + GEMCallbackData getCallbackData(); // Get GEMCallbackData struct associated with menu item + GEM_VIRTUAL GEMItem& setTitle(const char* title_); // Set title of the menu item + GEM_VIRTUAL const char* getTitle(); // Get title of the menu item + GEMItem& setPrecision(byte prec); // Explicitly set precision for float or double variables as required by dtostrf() conversion, + // i.e. the number of digits after the decimal sign + GEMItem& setAdjustedASCIIOrder(bool mode = true); // Turn adjsuted order of characters when editing char[17] variables on (with space character followed by `a` and preceded by `) or off + GEMItem& setReadonly(bool mode = true); // Explicitly set or unset readonly mode for variable that menu item is associated with + // (relevant for GEM_VAL_INTEGER, GEM_VAL_BYTE, GEM_VAL_FLOAT, GEM_VAL_DOUBLE, GEM_VAL_CHAR, + // GEM_VAL_BOOL variable menu items, GEM_VAL_SELECT option select and GEM_VAL_SPINNER spinner), or menu button GEM_ITEM_BUTTON + // and menu link GEM_ITEM_LINK, pressing of which won't result in any action, associated with them + bool getReadonly(); // Get readonly state of the variable that menu item is associated with (as well as menu link or button) + GEMItem& hide(bool hide = true); // Explicitly hide or show menu item + GEMItem& show(); // Explicitly show menu item + bool getHidden(); // Get hidden state of the menu item + GEMItem& remove(); // Remove menu item from parent menu page + GEM_VIRTUAL void* getLinkedVariablePointer(); // Get pointer to a linked variable (relevant for menu items that represent variable) + GEM_VIRTUAL GEMItem* getMenuItemNext(bool total = false); // Get next menu item (including hidden ones if total set to true) + protected: const char* title; void* linkedVariable = nullptr; byte linkedType; byte type; byte precision = GEM_FLOAT_PREC; - boolean readonly = false; - boolean hidden = false; + bool adjustedAsciiOrder = false; + bool readonly = false; + bool hidden = false; GEMSelect* select = nullptr; + #ifdef GEM_SUPPORT_SPINNER + GEMSpinner* spinner = nullptr; + #endif GEMPage* parentPage = nullptr; GEMPage* linkedPage = nullptr; GEMItem* menuItemNext = nullptr; - boolean callbackWithArgs = false; union { void (*callbackAction)() = nullptr; void (*callbackActionArg)(GEMCallbackData); }; + bool callbackWithArgs = false; GEMCallbackData callbackData; - GEMItem* getMenuItemNext(); // Get next menu item, excluding hidden ones }; #endif diff --git a/src/GEMPage.cpp b/src/GEMPage.cpp index 2dd335c..d1224a8 100644 --- a/src/GEMPage.cpp +++ b/src/GEMPage.cpp @@ -2,7 +2,7 @@ GEMPage - menu page (or level) for GEM library. Consists of a list of menu items. GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -16,7 +16,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2023 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -37,56 +37,90 @@ #include #include "GEMPage.h" +GEMPage::GEMPage(const char* title_) + : title(title_) +{ } + GEMPage::GEMPage(const char* title_, void (*exitAction_)()) : title(title_) , exitAction(exitAction_) { } -void GEMPage::addMenuItem(GEMItem& menuItem) { +GEMPage::GEMPage(const char* title_, GEMPage& parentMenuPage_) + : title(title_) +{ + setParentMenuPage(parentMenuPage_); +} + +GEMPage& GEMPage::addMenuItem(GEMItem& menuItem, byte pos, bool total) { // Prevent adding menu item that was already added to another (or the same) page if (menuItem.parentPage == nullptr) { - if (itemsCountTotal == 0) { - // If menu page is empty, link supplied menu item from within page directly (this will be the first menu item in a page) - _menuItem = &menuItem; + byte itemsMax = total ? itemsCountTotal : itemsCount; + if (pos >= itemsMax) { + // Cap maximum pos at number of items + pos = itemsMax; + } else if (_menuItemBack.linkedPage != nullptr && pos == 0) { + // Prevent adding supplied menu item in place of Back button + pos = 1; + } + if (pos > 0) { + // If custom position is defined (and is within range), link supplied menu item from within preceding menu item + GEMItem* menuItemTmp = getMenuItem(pos-1, total); + menuItem.menuItemNext = menuItemTmp->menuItemNext; + menuItemTmp->menuItemNext = &menuItem; } else { - // If menu page is not empty, link supplied menu item from within the last menu item of the page - getMenuItem(itemsCountTotal-1, true)->menuItemNext = &menuItem; + // Link supplied menu item as a first menu item on a page + menuItem.menuItemNext = _menuItem; + _menuItem = &menuItem; } menuItem.parentPage = this; + itemsCountTotal++; if (!menuItem.hidden) { itemsCount++; + currentItemNum = (_menuItemBack.linkedPage != nullptr) ? 1 : 0; } - itemsCountTotal++; - currentItemNum = (_menuItemBack.linkedPage != nullptr) ? 1 : 0; } + return *this; } -void GEMPage::setParentMenuPage(GEMPage& parentMenuPage) { - _menuItemBack.type = GEM_ITEM_BACK; - _menuItemBack.linkedPage = &parentMenuPage; - // Back button menu item should be always inserted at first position in list - GEMItem* menuItemTmp = _menuItem; - _menuItem = &_menuItemBack; - if (menuItemTmp != nullptr) { - _menuItemBack.menuItemNext = menuItemTmp; +GEMPage& GEMPage::setParentMenuPage(GEMPage& parentMenuPage) { + if (_menuItemBack.linkedPage == nullptr) { + _menuItemBack.type = GEM_ITEM_BACK; + // Back button menu item should be always inserted at first position in list + GEMItem* menuItemTmp = _menuItem; + _menuItem = &_menuItemBack; + if (menuItemTmp != nullptr) { + _menuItemBack.menuItemNext = menuItemTmp; + } + itemsCount++; + itemsCountTotal++; + currentItemNum = (itemsCount > 1) ? 1 : 0; } - itemsCount++; - itemsCountTotal++; - currentItemNum = (itemsCount > 1) ? 1 : 0; + _menuItemBack.linkedPage = &parentMenuPage; + return *this; } -void GEMPage::setTitle(const char* title_) { +GEMPage& GEMPage::setTitle(const char* title_) { title = title_; + return *this; } const char* GEMPage::getTitle() { return title; } -GEMItem* GEMPage::getMenuItem(byte index, boolean total) { +GEMPage& GEMPage::setAppearance(GEMAppearance* appearance) { + _appearance = appearance; + return *this; +} + +GEMItem* GEMPage::getMenuItem(byte index, bool total) { GEMItem* menuItemTmp = (!total && _menuItem->hidden) ? _menuItem->getMenuItemNext() : _menuItem; for (byte i=0; imenuItemNext : menuItemTmp->getMenuItemNext(); + menuItemTmp = menuItemTmp->getMenuItemNext(total); + if (menuItemTmp == nullptr) { + return nullptr; + } } return menuItemTmp; } @@ -95,13 +129,17 @@ GEMItem* GEMPage::getCurrentMenuItem() { return getMenuItem(currentItemNum); } -int GEMPage::getMenuItemNum(GEMItem& menuItem) { - GEMItem* menuItemTmp = (_menuItem->hidden) ? _menuItem->getMenuItemNext() : _menuItem; - for (byte i=0; ihidden) ? _menuItem->getMenuItemNext() : _menuItem; + for (byte i=0; i<(total ? itemsCountTotal : itemsCount); i++) { if (menuItemTmp == &menuItem) { return i; } - menuItemTmp = menuItemTmp->getMenuItemNext(); + menuItemTmp = menuItemTmp->getMenuItemNext(total); } return -1; } @@ -133,3 +171,27 @@ void GEMPage::showMenuItem(GEMItem& menuItem) { currentItemNum = 1; } } + +void GEMPage::removeMenuItem(GEMItem& menuItem) { + int menuItemNum = getMenuItemNum(menuItem); + int menuItemNumTotal = getMenuItemNum(menuItem, true); + itemsCountTotal--; + if (!menuItem.hidden) { + itemsCount--; + if (menuItemNum <= currentItemNum) { + if (currentItemNum > 0) { + currentItemNum--; + } + } + } + if (_menuItemBack.linkedPage != nullptr && itemsCount == 1) { + currentItemNum = 0; + } + if (menuItemNumTotal > 0) { + getMenuItem(menuItemNumTotal-1, true)->menuItemNext = menuItem.menuItemNext; + } else { + _menuItem = menuItem.menuItemNext; + } + menuItem.parentPage = nullptr; + menuItem.menuItemNext = nullptr; +} \ No newline at end of file diff --git a/src/GEMPage.h b/src/GEMPage.h index d848fa6..9615977 100644 --- a/src/GEMPage.h +++ b/src/GEMPage.h @@ -2,7 +2,7 @@ GEMPage - menu page (or level) for GEM library. Consists of a list of menu items. GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -16,7 +16,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2023 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -38,8 +38,18 @@ #define HEADER_GEMPAGE #include +#include "GEMAppearance.h" #include "GEMItem.h" +// Macro constant (alias) for the last possible position that menu item can be added at +#define GEM_LAST_POS 255 + +// Macro constant (alias) for modifier of GEMPage::addMenuItem() method for the case when all menu items should be considered +#define GEM_ITEMS_TOTAL true + +// Macro constant (alias) for modifier of GEMPage::addMenuItem() method for the case when only visible menu items should be considered +#define GEM_ITEMS_VISIBLE false + // Declaration of GEMPage class class GEMPage { friend class GEM; @@ -50,26 +60,33 @@ class GEMPage { /* @param 'title_' - title of the menu page displayed at top of the screen @param 'exitAction_' - pointer to callback function executed when GEM_KEY_CANCEL is pressed while being on top level menu page + @param 'parentMenuPage_' - reference to parent level menu page (to know where to go back to when Back button is pressed) */ - GEMPage(const char* title_ = "", void (*exitAction_)() = nullptr); - void addMenuItem(GEMItem& menuItem); // Add menu item to menu page - void setParentMenuPage(GEMPage& parentMenuPage); // Specify parent level menu page (to know where to go back to when pressing Back button) - void setTitle(const char* title_); // Set title of the menu page - const char* getTitle(); // Get title of the menu page - private: + GEMPage(const char* title_); + GEMPage(const char* title_, void (*exitAction_)()); + GEMPage(const char* title_, GEMPage& parentMenuPage_); + GEM_VIRTUAL GEMPage& addMenuItem(GEMItem& menuItem, byte pos = GEM_LAST_POS, bool total = GEM_ITEMS_TOTAL); // Add menu item to menu page (optionally at specified index out of total or only visible items) + GEM_VIRTUAL GEMPage& setParentMenuPage(GEMPage& parentMenuPage); // Specify parent level menu page (to know where to go back to when Back button is pressed) + GEM_VIRTUAL GEMPage& setTitle(const char* title_); // Set title of the menu page + GEM_VIRTUAL const char* getTitle(); // Get title of the menu page + GEMPage& setAppearance(GEMAppearance* appearance); // Set appearance of the menu page + GEM_VIRTUAL GEMItem* getMenuItem(byte index, bool total = false); // Get pointer to menu item by index (counting hidden ones if total set to true) + GEM_VIRTUAL GEMItem* getCurrentMenuItem(); // Get pointer to current menu item + GEM_VIRTUAL byte getCurrentMenuItemIndex(); // Get index of current menu item + protected: const char* title; - byte currentItemNum = 0; // Currently selected (focused) menu item of the page - byte itemsCount = 0; // Items count excluding hidden ones - byte itemsCountTotal = 0; // Items count incuding hidden ones - GEMItem* getMenuItem(byte index, boolean total = false); - GEMItem* getCurrentMenuItem(); - int getMenuItemNum(GEMItem& menuItem); // Find index of the supplied menu item + byte currentItemNum = 0; // Currently selected (focused) menu item of the page + byte itemsCount = 0; // Items count excluding hidden ones + byte itemsCountTotal = 0; // Items count incuding hidden ones + GEM_VIRTUAL int getMenuItemNum(GEMItem& menuItem, bool total = false); // Find index of the supplied menu item void hideMenuItem(GEMItem& menuItem); void showMenuItem(GEMItem& menuItem); - GEMItem* _menuItem = nullptr; // First menu item of the page (the following ones are linked from within one another) - GEMItem _menuItemBack {"", static_cast(nullptr)}; // Local instance of Back button (created when parent level menu page is specified through - // setParentMenuPage(); always becomes the first menu item in a list) + GEM_VIRTUAL void removeMenuItem(GEMItem& menuItem); // Remove menu item from menu page + GEMItem* _menuItem = nullptr; // First menu item of the page (the following ones are linked from within one another) + GEMItem _menuItemBack {"", static_cast(nullptr)}; // Local instance of Back button (created when parent level menu page is specified through + // setParentMenuPage(); always becomes the first menu item in a list) void (*exitAction)() = nullptr; + GEMAppearance* _appearance = nullptr; }; #endif diff --git a/src/GEMSelect.cpp b/src/GEMSelect.cpp index b38fce9..f7b723a 100644 --- a/src/GEMSelect.cpp +++ b/src/GEMSelect.cpp @@ -2,7 +2,7 @@ GEMSelect - option select for GEM library. GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -16,7 +16,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2021 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2023 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -82,7 +82,7 @@ int GEMSelect::getSelectedOptionNum(void* variable) { SelectOptionChar* optsChar = (SelectOptionChar*)_options; SelectOptionFloat* optsFloat = (SelectOptionFloat*)_options; SelectOptionDouble* optsDouble = (SelectOptionDouble*)_options; - boolean found = false; + bool found = false; for (byte i=0; i<_length; i++) { switch (_type) { case GEM_VAL_INTEGER: @@ -134,6 +134,9 @@ const char* GEMSelect::getOptionNameByIndex(int index) { case GEM_VAL_DOUBLE: name = (index > -1 && index < _length) ? optsDouble[index].name : ""; break; + default: + name = ""; + break; } return name; } diff --git a/src/GEMSelect.h b/src/GEMSelect.h index 199ee9f..82b08ba 100644 --- a/src/GEMSelect.h +++ b/src/GEMSelect.h @@ -2,7 +2,7 @@ GEMSelect - option select for GEM library. GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -16,7 +16,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2021 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2023 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -37,6 +37,9 @@ #ifndef HEADER_GEMSELECT #define HEADER_GEMSELECT +#include "config.h" +#include "constants.h" + // Declaration of SelectOptionInt type struct SelectOptionInt { const char* name; // Text label of the option as displayed in select @@ -82,16 +85,16 @@ class GEMSelect { GEMSelect(byte length_, SelectOptionChar* options_); GEMSelect(byte length_, SelectOptionFloat* options_); GEMSelect(byte length_, SelectOptionDouble* options_); - private: + protected: byte _type; byte _length; void* _options; byte getType(); byte getLength(); - int getSelectedOptionNum(void* variable); - const char* getSelectedOptionName(void* variable); - const char* getOptionNameByIndex(int index); - void setValue(void* variable, int index); // Assign value of the selected option to supplied variable + GEM_VIRTUAL int getSelectedOptionNum(void* variable); + GEM_VIRTUAL const char* getSelectedOptionName(void* variable); + GEM_VIRTUAL const char* getOptionNameByIndex(int index); + GEM_VIRTUAL void setValue(void* variable, int index); // Assign value of the selected option to supplied variable }; #endif diff --git a/src/GEMSpinner.cpp b/src/GEMSpinner.cpp new file mode 100644 index 0000000..dcd925b --- /dev/null +++ b/src/GEMSpinner.cpp @@ -0,0 +1,184 @@ +/* + GEMSpinner - increment/decrement spinner for GEM library (similar to GEMSpinner). + + GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) + and option selects. User-defined callback function can be specified to invoke when menu item is saved. + + Supports buttons that can invoke user-defined actions and create action-specific + context, which can have its own enter (setup) and exit callbacks as well as loop function. + + Supports: + - AltSerialGraphicLCD library by Jon Green (http://www.jasspa.com/serialGLCD.html); + - U8g2 library by olikraus (https://github.com/olikraus/U8g2_Arduino); + - Adafruit GFX library by Adafruit (https://github.com/adafruit/Adafruit-GFX-Library). + + For documentation visit: + https://github.com/Spirik/GEM + + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov + + This file is part of GEM library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see . +*/ + +#include +#include "GEMSpinner.h" +#include "constants.h" + +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesByte boundaries_) + : _boundaries{ { .boundariesByte = { .step = boundaries_.step, .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } + , _type(GEM_VAL_BYTE) + , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) +{ } + +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesInt boundaries_) + : _boundaries{ { .boundariesInt = { .step = abs(boundaries_.step), .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } + , _type(GEM_VAL_INTEGER) + , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) +{ } + +#ifdef GEM_SUPPORT_FLOAT_EDIT +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesFloat boundaries_) + : _boundaries{ { .boundariesFloat = { .step = abs(boundaries_.step), .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } + , _type(GEM_VAL_FLOAT) + , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) +{ } + +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesDouble boundaries_) + : _boundaries{ { .boundariesDouble = { .step = abs(boundaries_.step), .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } + , _type(GEM_VAL_DOUBLE) + , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) +{ } +#endif + +byte GEMSpinner::getType() { + return _type; +} + +int GEMSpinner::getLength() { + return _length; +} + +int GEMSpinner::getSelectedOptionNum(void* variable) { + int num = -1; + switch (_type) { + case GEM_VAL_BYTE: + { + GEMSpinnerBoundariesByte boundaries = _boundaries.boundariesByte; + byte val = *(byte*)variable; + if (val >= boundaries.min && val <= boundaries.max) { + num = (val - boundaries.min) / boundaries.step; + } + } + break; + case GEM_VAL_INTEGER: + { + GEMSpinnerBoundariesInt boundaries = _boundaries.boundariesInt; + int val = *(int*)variable; + if (val >= boundaries.min && val <= boundaries.max) { + num = (val - boundaries.min) / boundaries.step; + } + } + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + { + GEMSpinnerBoundariesFloat boundaries = _boundaries.boundariesFloat; + float val = *(float*)variable; + if (val >= boundaries.min && val <= boundaries.max) { + num = (val - boundaries.min) / boundaries.step; + } + } + break; + case GEM_VAL_DOUBLE: + { + GEMSpinnerBoundariesDouble boundaries = _boundaries.boundariesDouble; + double val = *(double*)variable; + if (val >= boundaries.min && val <= boundaries.max) { + num = (val - boundaries.min) / boundaries.step; + } + } + break; + #endif + } + return num; +} + +GEMSpinnerValue GEMSpinner::getOptionNameByIndex(void* variable, int index) { + int selectedOptionNum = getSelectedOptionNum(variable); + GEMSpinnerValue value = { 0 }; + switch (_type) { + case GEM_VAL_BYTE: + if (selectedOptionNum > -1) { + GEMSpinnerBoundariesByte boundaries = _boundaries.boundariesByte; + byte val = *(byte*)variable + (index - selectedOptionNum) * boundaries.step; + value.valByte = val <= boundaries.max ? val : val - boundaries.step; + } else { + value.valByte = *(byte*)variable; + } + break; + case GEM_VAL_INTEGER: + if (selectedOptionNum > -1) { + GEMSpinnerBoundariesInt boundaries = _boundaries.boundariesInt; + int val = *(int*)variable + (index - selectedOptionNum) * boundaries.step; + value.valInt = val <= boundaries.max ? val : val - boundaries.step; + } else { + value.valInt = *(int*)variable; + } + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + if (selectedOptionNum > -1) { + GEMSpinnerBoundariesFloat boundaries = _boundaries.boundariesFloat; + float val = *(float*)variable + (index - selectedOptionNum) * boundaries.step; + value.valFloat = val <= boundaries.max ? val : val - boundaries.step; + } else { + value.valFloat = *(float*)variable; + } + break; + case GEM_VAL_DOUBLE: + if (selectedOptionNum > -1) { + GEMSpinnerBoundariesDouble boundaries = _boundaries.boundariesDouble; + double val = *(double*)variable + (index - selectedOptionNum) * boundaries.step; + value.valDouble = val <= boundaries.max ? val : val - boundaries.step; + } else { + value.valDouble = *(double*)variable; + } + break; + #endif + } + return value; +} + +void GEMSpinner::setValue(void* variable, int index) { + GEMSpinnerValue value = getOptionNameByIndex(variable, index); + switch (_type) { + case GEM_VAL_BYTE: + *(byte*)variable = value.valByte; + break; + case GEM_VAL_INTEGER: + *(int*)variable = value.valInt; + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + *(float*)variable = value.valFloat; + break; + case GEM_VAL_DOUBLE: + *(double*)variable = value.valDouble; + break; + #endif + } +} diff --git a/src/GEMSpinner.h b/src/GEMSpinner.h new file mode 100644 index 0000000..48987d7 --- /dev/null +++ b/src/GEMSpinner.h @@ -0,0 +1,123 @@ +/* + GEMSpinner - increment/decrement spinner for GEM library (similar to GEMSelect). + + GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) + and option selects. User-defined callback function can be specified to invoke when menu item is saved. + + Supports buttons that can invoke user-defined actions and create action-specific + context, which can have its own enter (setup) and exit callbacks as well as loop function. + + Supports: + - AltSerialGraphicLCD library by Jon Green (http://www.jasspa.com/serialGLCD.html); + - U8g2 library by olikraus (https://github.com/olikraus/U8g2_Arduino); + - Adafruit GFX library by Adafruit (https://github.com/adafruit/Adafruit-GFX-Library). + + For documentation visit: + https://github.com/Spirik/GEM + + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov + + This file is part of GEM library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see . +*/ + +#ifndef HEADER_GEMSPINNER +#define HEADER_GEMSPINNER + +#include "config.h" +#include "constants.h" + +// Declaration of GEMSpinnerBoundariesByte type +struct GEMSpinnerBoundariesByte { + byte step; // Step of the increment/decrement + byte min; // Min allowed value + byte max; // Max allowed value +}; + +// Declaration of GEMSpinnerBoundariesInt type +struct GEMSpinnerBoundariesInt { + int step; + int min; + int max; +}; + +#ifdef GEM_SUPPORT_FLOAT_EDIT +// Declaration of GEMSpinnerBoundariesFloat type +struct GEMSpinnerBoundariesFloat { + float step; + float min; + float max; +}; + +// Declaration of GEMSpinnerBoundariesDouble type +struct GEMSpinnerBoundariesDouble { + double step; + double min; + double max; +}; +#endif + +// Declaration of GEMSpinnerBoundaries type +struct GEMSpinnerBoundaries { + union { + GEMSpinnerBoundariesByte boundariesByte; + GEMSpinnerBoundariesInt boundariesInt; + #ifdef GEM_SUPPORT_FLOAT_EDIT + GEMSpinnerBoundariesFloat boundariesFloat; + GEMSpinnerBoundariesDouble boundariesDouble; + #endif + }; +}; + +// Declaration of GEMSpinnerValue type +struct GEMSpinnerValue { + union { + int valByte; + int valInt; + #ifdef GEM_SUPPORT_FLOAT_EDIT + double valFloat; + double valDouble; + #endif + }; +}; + +// Declaration of GEMSpinner class +class GEMSpinner { + friend class GEM; + friend class GEM_u8g2; + friend class GEM_adafruit_gfx; + public: + /* + @param 'boundaries_' - boundaries of the spinner of corresponding type + */ + GEMSpinner(GEMSpinnerBoundariesByte boundaries_); + GEMSpinner(GEMSpinnerBoundariesInt boundaries_); + #ifdef GEM_SUPPORT_FLOAT_EDIT + GEMSpinner(GEMSpinnerBoundariesFloat boundaries_); + GEMSpinner(GEMSpinnerBoundariesDouble boundaries_); + #endif + protected: + GEMSpinnerBoundaries _boundaries; + byte _type; + int _length; + byte getType(); + int getLength(); + GEM_VIRTUAL int getSelectedOptionNum(void* variable); + GEM_VIRTUAL GEMSpinnerValue getOptionNameByIndex(void* variable, int index); + GEM_VIRTUAL void setValue(void* variable, int index); // Assign value of the selected option to supplied variable +}; + +#endif diff --git a/src/GEM_adafruit_gfx.cpp b/src/GEM_adafruit_gfx.cpp index a2a93cd..7484b9f 100644 --- a/src/GEM_adafruit_gfx.cpp +++ b/src/GEM_adafruit_gfx.cpp @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -39,7 +39,7 @@ // AVR-based Arduinos have suppoort for dtostrf, some others may require manual inclusion (e.g. SAMD), // see https://github.com/plotly/arduino-api/issues/38#issuecomment-108987647 -#if defined(GEM_SUPPORT_FLOAT_EDIT) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)) +#if defined(GEM_SUPPORT_FLOAT_EDIT) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_NRF52840)) #include #endif @@ -50,8 +50,10 @@ #define GEM_CHAR_CODE_DOT 46 #define GEM_CHAR_CODE_SPACE 32 #define GEM_CHAR_CODE_UNDERSCORE 95 -#define GEM_CHAR_CODE_LINE 124 #define GEM_CHAR_CODE_TILDA 126 +#define GEM_CHAR_CODE_BANG 33 +#define GEM_CHAR_CODE_a 97 +#define GEM_CHAR_CODE_ACCENT 96 // Sprite of the default GEM _splash screen (GEM logo v1) /* @@ -64,6 +66,7 @@ static const uint8_t logo_bits [] PROGMEM = { */ // Sprite of the default GEM _splash screen (GEM logo v2) + #define logo_width 20 #define logo_height 8 static const uint8_t logo_bits [] PROGMEM = { @@ -71,8 +74,25 @@ static const uint8_t logo_bits [] PROGMEM = { 0x02, 0x20, 0xf9, 0xf2, 0x20, 0x00, 0x00, 0x00 }; +#define logo_width_scaled 40 +#define logo_height_scaled 16 +static const uint8_t logo_bits_scaled [] PROGMEM = { + 0xff, 0x03, 0xff, 0x0c, 0x0c, 0xff, 0x03, 0xff, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, + 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0x03, 0xc3, + 0xfc, 0x0c, 0xcc, 0x03, 0xc3, 0xfc, 0x0c, 0xcc, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, + 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0xff, 0xc3, 0xff, 0x0c, + 0x0c, 0xff, 0xc3, 0xff, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const Splash logo[] = { + {logo_width, logo_height, logo_bits}, + {logo_width_scaled, logo_height_scaled, logo_bits_scaled} +}; + // Sprites of the UI elements used to draw menu +#define sprite_height 8 + #define arrowRight_width 6 #define arrowRight_height 8 static const uint8_t arrowRight_bits [] PROGMEM = { @@ -109,17 +129,103 @@ static const uint8_t selectArrows_bits [] PROGMEM = { 0x00, 0x20, 0x70, 0x00, 0x70, 0x20, 0x00, 0x00 }; +#define sprite_height_scaled 16 + +#define arrowRight_width_scaled 12 +#define arrowRight_height_scaled 16 +static const uint8_t arrowRight_bits_scaled [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0e, 0x00, 0x0f, 0x00, 0x0f, 0x80, 0x0f, 0xc0, 0x0f, 0xc0, + 0x0f, 0x80, 0x0f, 0x00, 0x0e, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define arrowLeft_width_scaled 12 +#define arrowLeft_height_scaled 16 +static const uint8_t arrowLeft_bits_scaled [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x1c, 0x00, 0x3c, 0x00, 0x7c, 0x00, 0xfc, 0x00, 0xfc, 0x00, + 0x7c, 0x00, 0x3c, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define arrowBtn_width_scaled 12 +#define arrowBtn_height_scaled 16 +static const uint8_t arrowBtn_bits_scaled [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xf8, 0x00, 0xdc, 0x00, 0xce, 0x00, 0xc7, 0x00, 0xc7, 0x00, + 0xce, 0x00, 0xdc, 0x00, 0xf8, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +#define checkboxUnchecked_width_scaled 14 +#define checkboxUnchecked_height_scaled 16 +static const uint8_t checkboxUnchecked_bits_scaled [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0xff, 0xf0, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x30, + 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xff, 0xf0, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, +}; + +#define checkboxChecked_width_scaled 14 +#define checkboxChecked_height_scaled 16 +static const uint8_t checkboxChecked_bits_scaled [] PROGMEM = { + 0x00, 0x0c, 0x00, 0x1c, 0xff, 0x38, 0xfe, 0x70, 0xc0, 0xf0, 0xc1, 0xf0, 0xf3, 0xb0, 0xff, 0x30, + 0xde, 0x30, 0xcc, 0x30, 0xc0, 0x30, 0xc0, 0x30, 0xff, 0xf0, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, +}; + +#define selectArrows_width_scaled 12 +#define selectArrows_height_scaled 16 +static const uint8_t selectArrows_bits_scaled [] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x1e, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3f, 0x00, 0x3f, 0x00, 0x1e, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const Splash arrowRight[] = { + {arrowRight_width, arrowRight_height, arrowRight_bits}, + {arrowRight_width_scaled, arrowRight_height_scaled, arrowRight_bits_scaled} +}; + +const Splash arrowLeft[] = { + {arrowLeft_width, arrowLeft_height, arrowLeft_bits}, + {arrowLeft_width_scaled, arrowLeft_height_scaled, arrowLeft_bits_scaled} +}; + +const Splash arrowBtn[] = { + {arrowBtn_width, arrowBtn_height, arrowBtn_bits}, + {arrowBtn_width_scaled, arrowBtn_height_scaled, arrowBtn_bits_scaled} +}; + +const Splash checkboxUnchecked[] = { + {checkboxUnchecked_width, checkboxUnchecked_height, checkboxUnchecked_bits}, + {checkboxUnchecked_width_scaled, checkboxUnchecked_height_scaled, checkboxUnchecked_bits_scaled} +}; + +const Splash checkboxChecked[] = { + {checkboxChecked_width, checkboxChecked_height, checkboxChecked_bits}, + {checkboxChecked_width_scaled, checkboxChecked_height_scaled, checkboxChecked_bits_scaled} +}; + +const Splash selectArrows[] = { + {selectArrows_width, selectArrows_height, selectArrows_bits}, + {selectArrows_width_scaled, selectArrows_height_scaled, selectArrows_bits_scaled} +}; + GEM_adafruit_gfx::GEM_adafruit_gfx(Adafruit_GFX& agfx_, byte menuPointerType_, byte menuItemsPerScreen_, byte menuItemHeight_, byte menuPageScreenTopOffset_, byte menuValuesLeftOffset_) : _agfx(agfx_) - , _menuPointerType(menuPointerType_) - , _menuItemsPerScreen(menuItemsPerScreen_) - , _menuItemHeight(menuItemHeight_) - , _menuPageScreenTopOffset(menuPageScreenTopOffset_) - , _menuValuesLeftOffset(menuValuesLeftOffset_) { - _menuItemFontSize = _menuItemHeight >= 8 ? 0 : 1; - _menuItemInsetOffset = (_menuItemHeight - _menuItemFont[_menuItemFontSize].height) / 2; - _splash = {logo_width, logo_height, logo_bits}; + _appearance.menuPointerType = menuPointerType_; + _appearance.menuItemsPerScreen = menuItemsPerScreen_; + _appearance.menuItemHeight = menuItemHeight_; + _appearance.menuPageScreenTopOffset = menuPageScreenTopOffset_; + _appearance.menuValuesLeftOffset = menuValuesLeftOffset_; + _appearanceCurrent = &_appearance; + _splash = logo[_textSize > 1 ? 1 : 0]; + clearContext(); + _editValueMode = false; + _editValueCursorPosition = 0; + memset(_valueString, '\0', GEM_STR_LEN - 1); + _valueSelectNum = -1; +} + +GEM_adafruit_gfx::GEM_adafruit_gfx(Adafruit_GFX& agfx_, GEMAppearance appearance_) + : _agfx(agfx_) + , _appearance(appearance_) +{ + _appearanceCurrent = &_appearance; + _splash = logo[_textSize > 1 ? 1 : 0]; clearContext(); _editValueMode = false; _editValueCursorPosition = 0; @@ -127,39 +233,97 @@ GEM_adafruit_gfx::GEM_adafruit_gfx(Adafruit_GFX& agfx_, byte menuPointerType_, b _valueSelectNum = -1; } +//====================== APPEARANCE OPERATIONS + +GEM_adafruit_gfx& GEM_adafruit_gfx::setAppearance(GEMAppearance appearance) { + _appearance = appearance; + return *this; +} + +GEMAppearance* GEM_adafruit_gfx::getCurrentAppearance() { + return (_menuPageCurrent != nullptr && _menuPageCurrent->_appearance != nullptr) ? _menuPageCurrent->_appearance : &_appearance; +} + +byte GEM_adafruit_gfx::getMenuItemsPerScreen() { + return getCurrentAppearance()->menuItemsPerScreen == GEM_ITEMS_COUNT_AUTO ? (_agfx.height() - getCurrentAppearance()->menuPageScreenTopOffset) / getCurrentAppearance()->menuItemHeight : getCurrentAppearance()->menuItemsPerScreen; +} + +byte GEM_adafruit_gfx::getMenuItemFontSize() { + return getCurrentAppearance()->menuItemHeight >= _menuItemFont[0].height * _textSize ? 0 : 1; +} + +byte GEM_adafruit_gfx::getMenuItemTitleLength() { + return (getCurrentAppearance()->menuValuesLeftOffset - 5 * _textSize) / (_menuItemFont[getMenuItemFontSize()].width * _textSize); +} + +byte GEM_adafruit_gfx::getMenuItemValueLength() { + return (_agfx.width() - getCurrentAppearance()->menuValuesLeftOffset - 6 * _textSize) / (_menuItemFont[getMenuItemFontSize()].width * _textSize); +} + + //====================== INIT OPERATIONS -void GEM_adafruit_gfx::setSplash(byte width, byte height, const uint8_t *image) { +GEM_adafruit_gfx& GEM_adafruit_gfx::setSplash(byte width, byte height, const uint8_t *image) { _splash = {width, height, image}; + return *this; } -void GEM_adafruit_gfx::setSplashDelay(uint16_t value) { +GEM_adafruit_gfx& GEM_adafruit_gfx::setSplashDelay(uint16_t value) { _splashDelay = value; + return *this; } -void GEM_adafruit_gfx::hideVersion(boolean flag) { +GEM_adafruit_gfx& GEM_adafruit_gfx::hideVersion(bool flag) { _enableVersion = !flag; + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::setTextSize(uint8_t size) { + _textSize = size > 0 ? size : 1; + setSpriteSize(_textSize); + return *this; } -void GEM_adafruit_gfx::setForegroundColor(uint16_t color) { +GEM_adafruit_gfx& GEM_adafruit_gfx::setSpriteSize(uint8_t size) { + _spriteSize = size > 0 ? size : 1; + if (_splash.image == logo[0].image || _splash.image == logo[1].image) { + _splash = logo[_spriteSize > 1 ? 1 : 0]; + } + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::setFontBig(const GFXfont* font, uint8_t width, uint8_t height, uint8_t baselineOffset) { + _fontFamilies.big = font; + _menuItemFont[0] = {width, height, baselineOffset}; + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::setFontSmall(const GFXfont* font, uint8_t width, uint8_t height, uint8_t baselineOffset) { + _fontFamilies.small = font; + _menuItemFont[1] = {width, height, baselineOffset}; + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::setForegroundColor(uint16_t color) { _menuForegroundColor = color; + return *this; } -void GEM_adafruit_gfx::setBackgroundColor(uint16_t color) { +GEM_adafruit_gfx& GEM_adafruit_gfx::setBackgroundColor(uint16_t color) { _menuBackgroundColor = color; + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::invertKeysDuringEdit(bool invert) { + _invertKeysDuringEdit = invert; + return *this; } -void GEM_adafruit_gfx::init() { - _agfx.setTextSize(1); +GEM_adafruit_gfx& GEM_adafruit_gfx::init() { + _agfx.setTextSize(_textSize); _agfx.setTextWrap(false); _agfx.setTextColor(_menuForegroundColor); _agfx.fillScreen(_menuBackgroundColor); - - _menuItemTitleLength = (_menuValuesLeftOffset - 5) / _menuItemFont[_menuItemFontSize].width; - _menuItemValueLength = (_agfx.width() - _menuValuesLeftOffset - 6) / _menuItemFont[_menuItemFontSize].width; - if (_menuItemsPerScreen == GEM_ITEMS_COUNT_AUTO) { - _menuItemsPerScreen = (_agfx.height() - _menuPageScreenTopOffset) / _menuItemHeight; - } if (_splashDelay > 0) { @@ -168,10 +332,10 @@ void GEM_adafruit_gfx::init() { if (_enableVersion) { delay(_splashDelay / 2); _agfx.setFont(_fontFamilies.small); - byte x = _agfx.width() - strlen(GEM_VER)*4; + byte x = _agfx.width() - strlen(GEM_VER) * 4 * _textSize; byte y = _agfx.height() - 1; - if (_splash.image != logo_bits) { - _agfx.setCursor(x - 12, y); + if (_splash.image != logo[0].image && _splash.image != logo[1].image) { + _agfx.setCursor(x - 12 * _textSize, y); _agfx.print("GEM"); } else { _agfx.setCursor(x, y); @@ -185,46 +349,74 @@ void GEM_adafruit_gfx::init() { _agfx.fillScreen(_menuBackgroundColor); } + + return *this; } -void GEM_adafruit_gfx::reInit() { - _agfx.setTextSize(1); +GEM_adafruit_gfx& GEM_adafruit_gfx::reInit() { + _agfx.setTextSize(_textSize); _agfx.setTextWrap(false); _agfx.setTextColor(_menuForegroundColor); _agfx.fillScreen(_menuBackgroundColor); + return *this; } -void GEM_adafruit_gfx::setMenuPageCurrent(GEMPage& menuPageCurrent) { +GEM_adafruit_gfx& GEM_adafruit_gfx::setMenuPageCurrent(GEMPage& menuPageCurrent) { _menuPageCurrent = &menuPageCurrent; + return *this; +} + +GEMPage* GEM_adafruit_gfx::getCurrentMenuPage() { + return _menuPageCurrent; } //====================== CONTEXT OPERATIONS -void GEM_adafruit_gfx::clearContext() { +GEM_adafruit_gfx& GEM_adafruit_gfx::clearContext() { context.loop = nullptr; context.enter = nullptr; context.exit = nullptr; context.allowExit = true; + return *this; } //====================== DRAW OPERATIONS -void GEM_adafruit_gfx::drawMenu() { +GEM_adafruit_gfx& GEM_adafruit_gfx::drawMenu() { _agfx.fillScreen(_menuBackgroundColor); drawTitleBar(); printMenuItems(); drawMenuPointer(); drawScrollbar(); + if (drawMenuCallback != nullptr) { + drawMenuCallback(); + } + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::setDrawMenuCallback(void (*drawMenuCallback_)()) { + drawMenuCallback = drawMenuCallback_; + return *this; +} + +GEM_adafruit_gfx& GEM_adafruit_gfx::removeDrawMenuCallback() { + drawMenuCallback = nullptr; + return *this; } void GEM_adafruit_gfx::drawTitleBar() { _agfx.setFont(_fontFamilies.small); _agfx.setTextWrap(true); _agfx.setTextColor(_menuForegroundColor); - _agfx.setCursor(5, _menuItemFont[1].baselineOffset + 1); + _agfx.setCursor(5 * _textSize, _menuItemFont[1].baselineOffset * _textSize + 1); _agfx.print(_menuPageCurrent->title); _agfx.setTextWrap(false); - _agfx.setFont(_menuItemFontSize ? _fontFamilies.small : _fontFamilies.big); + _agfx.setFont(getMenuItemFontSize() ? _fontFamilies.small : _fontFamilies.big); +} + +void GEM_adafruit_gfx::drawSprite(int16_t x, int16_t y, const Splash sprite[], uint16_t color) { + byte variant = _spriteSize > 1 ? 1 : 0; + _agfx.drawBitmap(x, y, sprite[variant].image, sprite[variant].width, sprite[variant].height, color); } void GEM_adafruit_gfx::printMenuItemString(const char* str, byte num, byte startPos) { @@ -236,154 +428,195 @@ void GEM_adafruit_gfx::printMenuItemString(const char* str, byte num, byte start } void GEM_adafruit_gfx::printMenuItemTitle(const char* str, int offset) { - printMenuItemString(str, _menuItemTitleLength + offset); + printMenuItemString(str, getMenuItemTitleLength() + offset); } void GEM_adafruit_gfx::printMenuItemValue(const char* str, int offset, byte startPos) { - printMenuItemString(str, _menuItemValueLength + offset, startPos); + printMenuItemString(str, getMenuItemValueLength() + offset, startPos); } void GEM_adafruit_gfx::printMenuItemFull(const char* str, int offset) { - printMenuItemString(str, _menuItemTitleLength + _menuItemValueLength + offset); + printMenuItemString(str, getMenuItemTitleLength() + getMenuItemValueLength() + offset); } -byte GEM_adafruit_gfx::getMenuItemInsetOffset(boolean forSprite) { - return _menuItemInsetOffset + (forSprite ? (_menuItemFontSize ? -1 : 2) : -1 ); // With additional offset for 6x8 sprites to compensate for smaller font size +byte GEM_adafruit_gfx::getMenuItemInsetOffset(bool forSprite) { + byte menuItemFontSize = getMenuItemFontSize(); + byte spriteHeight = _spriteSize > 1 ? sprite_height_scaled : sprite_height; + byte menuItemInsetOffset = (getCurrentAppearance()->menuItemHeight - _menuItemFont[menuItemFontSize].height * _textSize) / 2; + return menuItemInsetOffset + (forSprite ? (_menuItemFont[menuItemFontSize].height * _textSize - spriteHeight) / 2 : -1 * _textSize); // With additional offset for sprites and text for better visual alignment } -byte GEM_adafruit_gfx::getCurrentItemTopOffset(boolean withInsetOffset, boolean forSprite) { - return (_menuPageCurrent->currentItemNum % _menuItemsPerScreen) * _menuItemHeight + _menuPageScreenTopOffset + (withInsetOffset ? getMenuItemInsetOffset(forSprite) : 0); +byte GEM_adafruit_gfx::getCurrentItemTopOffset(bool withInsetOffset, bool forSprite) { + return (_menuPageCurrent->currentItemNum % getMenuItemsPerScreen()) * getCurrentAppearance()->menuItemHeight + getCurrentAppearance()->menuPageScreenTopOffset + (withInsetOffset ? getMenuItemInsetOffset(forSprite) : 0); } void GEM_adafruit_gfx::printMenuItem(GEMItem* menuItemTmp, byte yText, byte yDraw, uint16_t color) { _agfx.setTextColor(color); switch (menuItemTmp->type) { case GEM_ITEM_VAL: - _agfx.setCursor(5, yText); - if (menuItemTmp->readonly) { - printMenuItemTitle(menuItemTmp->title, -1); - _agfx.print("^"); - } else { - printMenuItemTitle(menuItemTmp->title); - } + { + _agfx.setCursor(5 * _textSize, yText); + if (menuItemTmp->readonly) { + printMenuItemTitle(menuItemTmp->title, -1); + _agfx.print("^"); + } else { + printMenuItemTitle(menuItemTmp->title); + } - _agfx.setCursor(_menuValuesLeftOffset, yText); - switch (menuItemTmp->linkedType) { - case GEM_VAL_INTEGER: - itoa(*(int*)menuItemTmp->linkedVariable, _valueString, 10); - printMenuItemValue(_valueString); - break; - case GEM_VAL_BYTE: - itoa(*(byte*)menuItemTmp->linkedVariable, _valueString, 10); - printMenuItemValue(_valueString); - break; - case GEM_VAL_CHAR: - printMenuItemValue((char*)menuItemTmp->linkedVariable); - break; - case GEM_VAL_BOOLEAN: - if (*(boolean*)menuItemTmp->linkedVariable) { - _agfx.drawBitmap(_menuValuesLeftOffset, yDraw, checkboxChecked_bits, checkboxChecked_width, checkboxChecked_height, color); - } else { - _agfx.drawBitmap(_menuValuesLeftOffset, yDraw, checkboxUnchecked_bits, checkboxUnchecked_width, checkboxUnchecked_height, color); - } - break; - case GEM_VAL_SELECT: - { - GEMSelect* select = menuItemTmp->select; - printMenuItemValue(select->getSelectedOptionName(menuItemTmp->linkedVariable)); - _agfx.drawBitmap(_agfx.width() - 7, yDraw, selectArrows_bits, selectArrows_width, selectArrows_height, color); - } - break; - #ifdef GEM_SUPPORT_FLOAT_EDIT - case GEM_VAL_FLOAT: - // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards - dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); - printMenuItemValue(_valueString); - break; - case GEM_VAL_DOUBLE: - // sprintf(_valueString,"%.6f", *(double*)menuItemTmp->linkedVariable); // May work for non-AVR boards - dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); - printMenuItemValue(_valueString); - break; - #endif + byte menuValuesLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; + _agfx.setCursor(menuValuesLeftOffset, yText); + switch (menuItemTmp->linkedType) { + case GEM_VAL_INTEGER: + itoa(*(int*)menuItemTmp->linkedVariable, _valueString, 10); + printMenuItemValue(_valueString); + break; + case GEM_VAL_BYTE: + itoa(*(byte*)menuItemTmp->linkedVariable, _valueString, 10); + printMenuItemValue(_valueString); + break; + case GEM_VAL_CHAR: + printMenuItemValue((char*)menuItemTmp->linkedVariable); + break; + case GEM_VAL_BOOL: + if (*(bool*)menuItemTmp->linkedVariable) { + drawSprite(menuValuesLeftOffset, yDraw, checkboxChecked, color); + } else { + drawSprite(menuValuesLeftOffset, yDraw, checkboxUnchecked, color); + } + break; + case GEM_VAL_SELECT: + { + GEMSelect* select = menuItemTmp->select; + printMenuItemValue(select->getSelectedOptionName(menuItemTmp->linkedVariable)); + drawSprite(_agfx.width() - 7 * _spriteSize, yDraw, selectArrows, color); + } + break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + switch (spinner->getType()) { + case GEM_VAL_BYTE: + itoa(*(byte*)menuItemTmp->linkedVariable, _valueString, 10); + break; + case GEM_VAL_INTEGER: + itoa(*(int*)menuItemTmp->linkedVariable, _valueString, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + break; + case GEM_VAL_DOUBLE: + dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + break; + #endif + } + printMenuItemValue(_valueString); + drawSprite(_agfx.width() - 7 * _spriteSize, yDraw, selectArrows, color); + } + break; + #endif + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards + dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + printMenuItemValue(_valueString); + break; + case GEM_VAL_DOUBLE: + // sprintf(_valueString,"%.6f", *(double*)menuItemTmp->linkedVariable); // May work for non-AVR boards + dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, _valueString); + printMenuItemValue(_valueString); + break; + #endif + } + break; } - break; case GEM_ITEM_LINK: - _agfx.setCursor(5, yText); + _agfx.setCursor(5 * _textSize, yText); if (menuItemTmp->readonly) { printMenuItemFull(menuItemTmp->title, -1); _agfx.print("^"); } else { printMenuItemFull(menuItemTmp->title); } - _agfx.drawBitmap(_agfx.width() - 8, yDraw, arrowRight_bits, arrowRight_width, arrowRight_height, color); + drawSprite(_agfx.width() - 8 * _spriteSize, yDraw, arrowRight, color); break; case GEM_ITEM_BACK: - _agfx.setCursor(11, yText); - _agfx.drawBitmap(5, yDraw, arrowLeft_bits, arrowLeft_width, arrowLeft_height, color); + drawSprite(5 * _textSize + 2 * (_spriteSize > 1 ? 1 : 0), yDraw, arrowLeft, color); break; case GEM_ITEM_BUTTON: - _agfx.setCursor(11, yText); + byte variant = _spriteSize > 1 ? 1 : 0; + _agfx.setCursor((5 * _textSize + arrowBtn[variant].width + 2 * variant), yText); if (menuItemTmp->readonly) { printMenuItemFull(menuItemTmp->title, -1); _agfx.print("^"); } else { printMenuItemFull(menuItemTmp->title); } - _agfx.drawBitmap(5, yDraw, arrowBtn_bits, arrowBtn_width, arrowBtn_height, color); + drawSprite(5 * _textSize + 2 * variant, yDraw, arrowBtn, color); break; } _agfx.setTextColor(_menuForegroundColor); } void GEM_adafruit_gfx::printMenuItems() { - byte currentPageScreenNum = _menuPageCurrent->currentItemNum / _menuItemsPerScreen; - GEMItem* menuItemTmp = _menuPageCurrent->getMenuItem(currentPageScreenNum * _menuItemsPerScreen); - byte y = _menuPageScreenTopOffset; + byte menuItemsPerScreen = getMenuItemsPerScreen(); + byte currentPageScreenNum = _menuPageCurrent->currentItemNum / menuItemsPerScreen; + GEMItem* menuItemTmp = _menuPageCurrent->getMenuItem(currentPageScreenNum * menuItemsPerScreen); + byte y = getCurrentAppearance()->menuPageScreenTopOffset; byte i = 0; - while (menuItemTmp != nullptr && i < _menuItemsPerScreen) { - byte yText = y + getMenuItemInsetOffset() + _menuItemFont[_menuItemFontSize].baselineOffset; + while (menuItemTmp != nullptr && i < menuItemsPerScreen) { + byte yText = y + getMenuItemInsetOffset() + _menuItemFont[getMenuItemFontSize()].baselineOffset * _textSize; byte yDraw = y + getMenuItemInsetOffset(true); printMenuItem(menuItemTmp, yText, yDraw, _menuForegroundColor); menuItemTmp = menuItemTmp->getMenuItemNext(); - y += _menuItemHeight; + y += getCurrentAppearance()->menuItemHeight; i++; } memset(_valueString, '\0', GEM_STR_LEN - 1); } -void GEM_adafruit_gfx::drawMenuPointer(boolean clear) { +void GEM_adafruit_gfx::drawMenuPointer(bool clear) { if (_menuPageCurrent->itemsCount > 0) { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); int pointerPosition = getCurrentItemTopOffset(false); - if (_menuPointerType == GEM_POINTER_DASH) { - _agfx.fillRect(0, _menuPageScreenTopOffset, 2, _agfx.height() - _menuPageScreenTopOffset, _menuBackgroundColor); + byte menuItemHeight = getCurrentAppearance()->menuItemHeight; + if (getCurrentAppearance()->menuPointerType == GEM_POINTER_DASH) { + byte menuPageScreenTopOffset = getCurrentAppearance()->menuPageScreenTopOffset; + _agfx.fillRect(0, menuPageScreenTopOffset, 2 * _spriteSize, _agfx.height() - menuPageScreenTopOffset, _menuBackgroundColor); if (menuItemTmp->readonly) { - for (byte i = 0; i < (_menuItemHeight - 1) / 2; i++) { + for (byte i = 0; i < (menuItemHeight - 1) / 2; i++) { _agfx.drawPixel(0, pointerPosition + i * 2, _menuForegroundColor); _agfx.drawPixel(1, pointerPosition + i * 2 + 1, _menuForegroundColor); + if (_spriteSize > 1) { + _agfx.drawPixel(2, pointerPosition + i * 2, _menuForegroundColor); + _agfx.drawPixel(3, pointerPosition + i * 2 + 1, _menuForegroundColor); + } } } else { - _agfx.fillRect(0, pointerPosition, 2, _menuItemHeight - 1, _menuForegroundColor); + _agfx.fillRect(0, pointerPosition, 2 * _spriteSize, menuItemHeight - 1, _menuForegroundColor); } if (clear) { - byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[_menuItemFontSize].baselineOffset; + byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[getMenuItemFontSize()].baselineOffset * _textSize; byte yDraw = pointerPosition + getMenuItemInsetOffset(true); - _agfx.fillRect(5, pointerPosition - 1, _agfx.width() - 2, _menuItemHeight + 1, _menuBackgroundColor); + _agfx.fillRect(5 * _spriteSize, pointerPosition - 1, _agfx.width() - 2, menuItemHeight + 1, _menuBackgroundColor); printMenuItem(menuItemTmp, yText, yDraw, _menuForegroundColor); } } else { - byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[_menuItemFontSize].baselineOffset; + byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[getMenuItemFontSize()].baselineOffset * _textSize; byte yDraw = pointerPosition + getMenuItemInsetOffset(true); - byte screensCount = (_menuPageCurrent->itemsCount % _menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / _menuItemsPerScreen : _menuPageCurrent->itemsCount / _menuItemsPerScreen + 1; - _agfx.fillRect(0, pointerPosition - 1, _agfx.width() + (screensCount > 1 ? -2 : 0), _menuItemHeight + 1, clear ? _menuBackgroundColor : _menuForegroundColor); + _agfx.fillRect(0, pointerPosition - 1, _agfx.width() - 2, menuItemHeight + 1, clear ? _menuBackgroundColor : _menuForegroundColor); printMenuItem(menuItemTmp, yText, yDraw, clear ? _menuForegroundColor : _menuBackgroundColor); if (menuItemTmp->readonly) { - for (byte i = 0; i < (_menuItemHeight + 2) / 2; i++) { + for (byte i = 0; i < (menuItemHeight + 2) / 2; i++) { _agfx.drawPixel(0, pointerPosition + i * 2, _menuBackgroundColor); _agfx.drawPixel(1, pointerPosition + i * 2 - 1, _menuBackgroundColor); + if (_spriteSize > 1) { + _agfx.drawPixel(2, pointerPosition + i * 2, _menuBackgroundColor); + _agfx.drawPixel(3, pointerPosition + i * 2 - 1, _menuBackgroundColor); + } } } } @@ -391,11 +624,13 @@ void GEM_adafruit_gfx::drawMenuPointer(boolean clear) { } void GEM_adafruit_gfx::drawScrollbar() { - byte screensCount = (_menuPageCurrent->itemsCount % _menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / _menuItemsPerScreen : _menuPageCurrent->itemsCount / _menuItemsPerScreen + 1; + byte menuItemsPerScreen = getMenuItemsPerScreen(); + byte screensCount = (_menuPageCurrent->itemsCount % menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / menuItemsPerScreen : _menuPageCurrent->itemsCount / menuItemsPerScreen + 1; if (screensCount > 1) { - byte currentScreenNum = _menuPageCurrent->currentItemNum / _menuItemsPerScreen; - byte scrollbarHeight = (_agfx.height() - _menuPageScreenTopOffset + 1) / screensCount; - byte scrollbarPosition = currentScreenNum * scrollbarHeight + _menuPageScreenTopOffset - 1; + byte currentScreenNum = _menuPageCurrent->currentItemNum / menuItemsPerScreen; + byte menuPageScreenTopOffset = getCurrentAppearance()->menuPageScreenTopOffset; + byte scrollbarHeight = (_agfx.height() - menuPageScreenTopOffset + 1) / screensCount; + byte scrollbarPosition = currentScreenNum * scrollbarHeight + menuPageScreenTopOffset - 1; _agfx.drawLine(_agfx.width() - 1, scrollbarPosition, _agfx.width() - 1, scrollbarPosition + scrollbarHeight, _menuForegroundColor); } } @@ -403,7 +638,7 @@ void GEM_adafruit_gfx::drawScrollbar() { //====================== MENU ITEMS NAVIGATION void GEM_adafruit_gfx::nextMenuItem() { - if (_menuPointerType != GEM_POINTER_DASH) { + if (getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) { drawMenuPointer(true); } if (_menuPageCurrent->currentItemNum == _menuPageCurrent->itemsCount-1) { @@ -411,7 +646,8 @@ void GEM_adafruit_gfx::nextMenuItem() { } else { _menuPageCurrent->currentItemNum++; } - boolean redrawMenu = (_menuPageCurrent->itemsCount > _menuItemsPerScreen && _menuPageCurrent->currentItemNum % _menuItemsPerScreen == 0); + byte menuItemsPerScreen = getMenuItemsPerScreen(); + bool redrawMenu = (_menuPageCurrent->itemsCount > menuItemsPerScreen && _menuPageCurrent->currentItemNum % menuItemsPerScreen == 0); if (redrawMenu) { drawMenu(); } else { @@ -420,10 +656,11 @@ void GEM_adafruit_gfx::nextMenuItem() { } void GEM_adafruit_gfx::prevMenuItem() { - if (_menuPointerType != GEM_POINTER_DASH) { + if (getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) { drawMenuPointer(true); } - boolean redrawMenu = (_menuPageCurrent->itemsCount > _menuItemsPerScreen && _menuPageCurrent->currentItemNum % _menuItemsPerScreen == 0); + byte menuItemsPerScreen = getMenuItemsPerScreen(); + bool redrawMenu = (_menuPageCurrent->itemsCount > menuItemsPerScreen && _menuPageCurrent->currentItemNum % menuItemsPerScreen == 0); if (_menuPageCurrent->currentItemNum == 0) { _menuPageCurrent->currentItemNum = _menuPageCurrent->itemsCount-1; } else { @@ -475,7 +712,7 @@ void GEM_adafruit_gfx::enterEditValueMode() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); _editValueType = menuItemTmp->linkedType; - if ((_menuPointerType != GEM_POINTER_DASH) && (_editValueType != GEM_VAL_BOOLEAN)) { + if ((getCurrentAppearance()->menuPointerType != GEM_POINTER_DASH) && (_editValueType != GEM_VAL_BOOL)) { drawMenuPointer(true); } switch (_editValueType) { @@ -494,7 +731,7 @@ void GEM_adafruit_gfx::enterEditValueMode() { _editValueLength = GEM_STR_LEN - 1; initEditValueCursor(); break; - case GEM_VAL_BOOLEAN: + case GEM_VAL_BOOL: checkboxToggle(); break; case GEM_VAL_SELECT: @@ -504,6 +741,15 @@ void GEM_adafruit_gfx::enterEditValueMode() { initEditValueCursor(); } break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + _valueSelectNum = spinner->getSelectedOptionNum(menuItemTmp->linkedVariable); + initEditValueCursor(); + } + break; + #endif #ifdef GEM_SUPPORT_FLOAT_EDIT case GEM_VAL_FLOAT: // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards @@ -524,8 +770,8 @@ void GEM_adafruit_gfx::enterEditValueMode() { void GEM_adafruit_gfx::checkboxToggle() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); int topOffset = getCurrentItemTopOffset(true, true); - boolean checkboxValue = *(boolean*)menuItemTmp->linkedVariable; - *(boolean*)menuItemTmp->linkedVariable = !checkboxValue; + bool checkboxValue = *(bool*)menuItemTmp->linkedVariable; + *(bool*)menuItemTmp->linkedVariable = !checkboxValue; if (menuItemTmp->callbackAction != nullptr) { if (menuItemTmp->callbackWithArgs) { menuItemTmp->callbackActionArg(menuItemTmp->callbackData); @@ -534,14 +780,17 @@ void GEM_adafruit_gfx::checkboxToggle() { } exitEditValue(); } else { - uint16_t foreColor = (_menuPointerType == GEM_POINTER_DASH) ? _menuForegroundColor : _menuBackgroundColor; - uint16_t backColor = (_menuPointerType == GEM_POINTER_DASH) ? _menuBackgroundColor : _menuForegroundColor; + byte menuPointerType = getCurrentAppearance()->menuPointerType; + uint16_t foreColor = (menuPointerType == GEM_POINTER_DASH) ? _menuForegroundColor : _menuBackgroundColor; + uint16_t backColor = (menuPointerType == GEM_POINTER_DASH) ? _menuBackgroundColor : _menuForegroundColor; + byte variant = _spriteSize > 1 ? 1 : 0; + byte menuValuesLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; if (!checkboxValue) { - _agfx.fillRect(_menuValuesLeftOffset, topOffset, checkboxChecked_width, checkboxChecked_height, backColor); - _agfx.drawBitmap(_menuValuesLeftOffset, topOffset, checkboxChecked_bits, checkboxChecked_width, checkboxChecked_height, foreColor); + _agfx.fillRect(menuValuesLeftOffset, topOffset, checkboxChecked[variant].width, checkboxChecked[variant].height, backColor); + drawSprite(menuValuesLeftOffset, topOffset, checkboxChecked, foreColor); } else { - _agfx.fillRect(_menuValuesLeftOffset, topOffset, checkboxUnchecked_width, checkboxUnchecked_height, backColor); - _agfx.drawBitmap(_menuValuesLeftOffset, topOffset, checkboxUnchecked_bits, checkboxUnchecked_width, checkboxUnchecked_height, foreColor); + _agfx.fillRect(menuValuesLeftOffset, topOffset, checkboxUnchecked[variant].width, checkboxUnchecked[variant].height, backColor); + drawSprite(menuValuesLeftOffset, topOffset, checkboxUnchecked, foreColor); } _editValueMode = false; } @@ -549,14 +798,14 @@ void GEM_adafruit_gfx::checkboxToggle() { void GEM_adafruit_gfx::clearValueVisibleRange() { int pointerPosition = getCurrentItemTopOffset(false); - byte cursorLeftOffset = _menuValuesLeftOffset; - _agfx.fillRect(cursorLeftOffset - 1, pointerPosition - 1, _agfx.width() - cursorLeftOffset - 1, _menuItemHeight + 1, _menuBackgroundColor); + byte cursorLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; + _agfx.fillRect(cursorLeftOffset - 1, pointerPosition - 1, _agfx.width() - cursorLeftOffset - 1, getCurrentAppearance()->menuItemHeight + 1, _menuBackgroundColor); } void GEM_adafruit_gfx::initEditValueCursor() { _editValueCursorPosition = 0; _editValueVirtualCursorPosition = 0; - if (_editValueType == GEM_VAL_SELECT) { + if (_editValueType == GEM_VAL_SELECT || _editValueType == GEM_VAL_SPINNER) { drawEditValueSelect(); } else { char chr = _valueString[_editValueVirtualCursorPosition]; @@ -567,12 +816,13 @@ void GEM_adafruit_gfx::initEditValueCursor() { void GEM_adafruit_gfx::nextEditValueCursorPosition() { char chr = _valueString[_editValueVirtualCursorPosition]; drawEditValueDigit(chr, true); - if ((_editValueCursorPosition != _menuItemValueLength - 1) && (_editValueCursorPosition != _editValueLength - 1) && (_valueString[_editValueCursorPosition] != '\0')) { + byte menuItemValueLength = getMenuItemValueLength(); + if ((_editValueCursorPosition != menuItemValueLength - 1) && (_editValueCursorPosition != _editValueLength - 1) && (_valueString[_editValueCursorPosition] != '\0')) { _editValueCursorPosition++; } if ((_editValueVirtualCursorPosition != _editValueLength - 1) && (_valueString[_editValueVirtualCursorPosition] != '\0')) { _editValueVirtualCursorPosition++; - if (_editValueCursorPosition == _menuItemValueLength - 1) { + if (_editValueCursorPosition == menuItemValueLength - 1) { clearValueVisibleRange(); printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); } @@ -598,35 +848,55 @@ void GEM_adafruit_gfx::prevEditValueCursorPosition() { drawEditValueDigit(chr); } -void GEM_adafruit_gfx::drawEditValueCursor(boolean clear) { +void GEM_adafruit_gfx::drawEditValueCursor(bool clear) { int pointerPosition = getCurrentItemTopOffset(false); - byte cursorLeftOffset = _menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[_menuItemFontSize].width; - if (_editValueType == GEM_VAL_SELECT) { - _agfx.fillRect(cursorLeftOffset - 1, pointerPosition - 1, _agfx.width() - cursorLeftOffset - 1, _menuItemHeight + 1, clear ? _menuBackgroundColor : _menuForegroundColor); + byte menuItemFontSize = getMenuItemFontSize(); + byte menuValuesLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; + byte cursorLeftOffset = menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[menuItemFontSize].width * _textSize; + if (_editValueType == GEM_VAL_SELECT || _editValueType == GEM_VAL_SPINNER) { + _agfx.fillRect(cursorLeftOffset - 1, pointerPosition - 1, _agfx.width() - cursorLeftOffset - 1, getCurrentAppearance()->menuItemHeight + 1, clear ? _menuBackgroundColor : _menuForegroundColor); } else { - _agfx.fillRect(cursorLeftOffset - 1, pointerPosition - 1, _menuItemFont[_menuItemFontSize].width + 1, _menuItemHeight + 1, clear ? _menuBackgroundColor : _menuForegroundColor); - byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[_menuItemFontSize].baselineOffset; - _agfx.setCursor(_menuValuesLeftOffset, yText); + _agfx.fillRect(cursorLeftOffset - 1, pointerPosition - 1, _menuItemFont[menuItemFontSize].width * _textSize + 1, getCurrentAppearance()->menuItemHeight + 1, clear ? _menuBackgroundColor : _menuForegroundColor); + byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[menuItemFontSize].baselineOffset * _textSize; + _agfx.setCursor(menuValuesLeftOffset, yText); } } void GEM_adafruit_gfx::nextEditValueDigit() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); char chr = _valueString[_editValueVirtualCursorPosition]; byte code = (byte)chr; if (_editValueType == GEM_VAL_CHAR) { - switch (code) { - case 0: - code = GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_TILDA: - code = GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_LINE - 1: - code = GEM_CHAR_CODE_LINE + 1; - break; - default: - code++; - break; + if (menuItemTmp->adjustedAsciiOrder) { + switch (code) { + case 0: + code = GEM_CHAR_CODE_a; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_a; + break; + case GEM_CHAR_CODE_ACCENT: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_TILDA: + code = GEM_CHAR_CODE_BANG; + break; + default: + code++; + break; + } + } else { + switch (code) { + case 0: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_TILDA: + code = GEM_CHAR_CODE_SPACE; + break; + default: + code++; + break; + } } } else { switch (code) { @@ -654,22 +924,40 @@ void GEM_adafruit_gfx::nextEditValueDigit() { } void GEM_adafruit_gfx::prevEditValueDigit() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); char chr = _valueString[_editValueVirtualCursorPosition]; byte code = (byte)chr; if (_editValueType == GEM_VAL_CHAR) { - switch (code) { - case 0: - code = GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_SPACE: - code = GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_LINE + 1: - code = GEM_CHAR_CODE_LINE - 1; - break; - default: - code--; - break; + if (menuItemTmp->adjustedAsciiOrder) { + switch (code) { + case 0: + code = GEM_CHAR_CODE_ACCENT; + break; + case GEM_CHAR_CODE_BANG: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_a: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_ACCENT; + break; + default: + code--; + break; + } + } else { + switch (code) { + case 0: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_TILDA; + break; + default: + code--; + break; + } } } else { switch (code) { @@ -696,17 +984,18 @@ void GEM_adafruit_gfx::prevEditValueDigit() { drawEditValueDigit(code); } -void GEM_adafruit_gfx::drawEditValueDigit(byte code, boolean clear) { +void GEM_adafruit_gfx::drawEditValueDigit(byte code, bool clear) { drawEditValueCursor(clear); uint16_t foreColor = (clear) ? _menuForegroundColor : _menuBackgroundColor; uint16_t backColor = (clear) ? _menuBackgroundColor : _menuForegroundColor; int pointerPosition = getCurrentItemTopOffset(false); - byte xText = _menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[_menuItemFontSize].width; - byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[_menuItemFontSize].baselineOffset; + byte menuItemFontSize = getMenuItemFontSize(); + byte xText = getCurrentAppearance()->menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[menuItemFontSize].width * _textSize; + byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[menuItemFontSize].baselineOffset * _textSize; char chrNew = (char)code; if (chrNew != '\0') { _valueString[_editValueVirtualCursorPosition] = chrNew; - _agfx.drawChar(xText, yText, code, foreColor, backColor, 1); + _agfx.drawChar(xText, yText, code, foreColor, backColor, _textSize); } } @@ -720,32 +1009,77 @@ void GEM_adafruit_gfx::nextEditValueSelect() { } void GEM_adafruit_gfx::prevEditValueSelect() { - GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - GEMSelect* select = menuItemTmp->select; if (_valueSelectNum > 0) { _valueSelectNum--; } drawEditValueSelect(); } +#ifdef GEM_SUPPORT_SPINNER +void GEM_adafruit_gfx::nextEditValueSpinner() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); + GEMSpinner* spinner = menuItemTmp->spinner; + if (_valueSelectNum+1 < spinner->getLength()) { + _valueSelectNum++; + } + drawEditValueSelect(); +} + +void GEM_adafruit_gfx::prevEditValueSpinner() { + prevEditValueSelect(); +} +#endif + void GEM_adafruit_gfx::drawEditValueSelect() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - GEMSelect* select = menuItemTmp->select; drawEditValueCursor(); _agfx.setTextColor(_menuBackgroundColor); int pointerPosition = getCurrentItemTopOffset(false); - byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[_menuItemFontSize].baselineOffset; - _agfx.setCursor(_menuValuesLeftOffset, yText); + byte yText = pointerPosition + getMenuItemInsetOffset() + _menuItemFont[getMenuItemFontSize()].baselineOffset * _textSize; + _agfx.setCursor(getCurrentAppearance()->menuValuesLeftOffset, yText); - printMenuItemValue(select->getOptionNameByIndex(_valueSelectNum)); - _agfx.drawBitmap(_agfx.width() - 7, getCurrentItemTopOffset(true, true), selectArrows_bits, selectArrows_width, selectArrows_height, _menuBackgroundColor); + switch (menuItemTmp->linkedType) { + case GEM_VAL_SELECT: + { + GEMSelect* select = menuItemTmp->select; + printMenuItemValue(select->getOptionNameByIndex(_valueSelectNum)); + } + break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + char valueStringTmp[GEM_STR_LEN]; + GEMSpinner* spinner = menuItemTmp->spinner; + GEMSpinnerValue valueTmp = spinner->getOptionNameByIndex(menuItemTmp->linkedVariable, _valueSelectNum); + switch (spinner->getType()) { + case GEM_VAL_BYTE: + itoa(valueTmp.valByte, valueStringTmp, 10); + break; + case GEM_VAL_INTEGER: + itoa(valueTmp.valInt, valueStringTmp, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(valueTmp.valFloat, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + case GEM_VAL_DOUBLE: + dtostrf(valueTmp.valDouble, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + #endif + } + printMenuItemValue(valueStringTmp); + } + break; + #endif + } + + drawSprite(_agfx.width() - 7 * _spriteSize, getCurrentItemTopOffset(true, true), selectArrows, _menuBackgroundColor); _agfx.setTextColor(_menuForegroundColor); } void GEM_adafruit_gfx::saveEditValue() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - void* temp; switch (menuItemTmp->linkedType) { case GEM_VAL_INTEGER: *(int*)menuItemTmp->linkedVariable = atoi(_valueString); @@ -762,6 +1096,14 @@ void GEM_adafruit_gfx::saveEditValue() { select->setValue(menuItemTmp->linkedVariable, _valueSelectNum); } break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + spinner->setValue(menuItemTmp->linkedVariable, _valueSelectNum); + } + break; + #endif #ifdef GEM_SUPPORT_FLOAT_EDIT case GEM_VAL_FLOAT: *(float*)menuItemTmp->linkedVariable = atof(_valueString); @@ -779,7 +1121,8 @@ void GEM_adafruit_gfx::saveEditValue() { } exitEditValue(); } else { - exitEditValue(false); + // exitEditValue(false); // Can speed up work of Adafruit GFX version of GEM on UNO R3, but disabled to be in line with other GEM versions + exitEditValue(); } } @@ -787,17 +1130,21 @@ void GEM_adafruit_gfx::cancelEditValue() { exitEditValue(false); } -void GEM_adafruit_gfx::exitEditValue(boolean redrawMenu) { +void GEM_adafruit_gfx::exitEditValue(bool redrawMenu) { memset(_valueString, '\0', GEM_STR_LEN - 1); _valueSelectNum = -1; _editValueMode = false; if (redrawMenu) { drawMenu(); } else { - drawMenuPointer(_menuPointerType == GEM_POINTER_DASH); + drawMenuPointer(getCurrentAppearance()->menuPointerType == GEM_POINTER_DASH); } } +bool GEM_adafruit_gfx::isEditMode() { + return _editValueMode; +} + // Trim leading/trailing whitespaces // Author: Adam Rosenfield, https://stackoverflow.com/a/122721 char* GEM_adafruit_gfx::trimString(char* str) { @@ -821,7 +1168,7 @@ char* GEM_adafruit_gfx::trimString(char* str) { //====================== KEY DETECTION -boolean GEM_adafruit_gfx::readyForKey() { +bool GEM_adafruit_gfx::readyForKey() { if ( (context.loop == nullptr) || ((context.loop != nullptr) && (context.allowExit)) ) { return true; @@ -832,9 +1179,10 @@ boolean GEM_adafruit_gfx::readyForKey() { } -void GEM_adafruit_gfx::registerKeyPress(byte keyCode) { +GEM_adafruit_gfx& GEM_adafruit_gfx::registerKeyPress(byte keyCode) { _currentKey = keyCode; dispatchKeyPress(); + return *this; } void GEM_adafruit_gfx::dispatchKeyPress() { @@ -858,24 +1206,36 @@ void GEM_adafruit_gfx::dispatchKeyPress() { case GEM_KEY_UP: if (_editValueType == GEM_VAL_SELECT) { prevEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + } else if (_editValueType == GEM_VAL_SPINNER) { + prevEditValueSpinner(); + #endif + } else if (_invertKeysDuringEdit) { + prevEditValueDigit(); } else { nextEditValueDigit(); } break; case GEM_KEY_RIGHT: - if (_editValueType != GEM_VAL_SELECT) { + if (_editValueType != GEM_VAL_SELECT && _editValueType != GEM_VAL_SPINNER) { nextEditValueCursorPosition(); } break; case GEM_KEY_DOWN: if (_editValueType == GEM_VAL_SELECT) { nextEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + } else if (_editValueType == GEM_VAL_SPINNER) { + nextEditValueSpinner(); + #endif + } else if (_invertKeysDuringEdit) { + nextEditValueDigit(); } else { prevEditValueDigit(); } break; case GEM_KEY_LEFT: - if (_editValueType != GEM_VAL_SELECT) { + if (_editValueType != GEM_VAL_SELECT && _editValueType != GEM_VAL_SPINNER) { prevEditValueCursorPosition(); } break; diff --git a/src/GEM_adafruit_gfx.h b/src/GEM_adafruit_gfx.h index 44f5cca..38adf38 100644 --- a/src/GEM_adafruit_gfx.h +++ b/src/GEM_adafruit_gfx.h @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -42,8 +42,13 @@ #include #include "fonts/TomThumbMono.h" #include "fonts/Fixed6x12.h" +#include "GEMAppearance.h" +#include "GEMContext.h" #include "GEMPage.h" #include "GEMSelect.h" +#ifdef GEM_SUPPORT_SPINNER +#include "GEMSpinner.h" +#endif #include "constants.h" // Macro constants (aliases) for Adafruit GFX font families used to draw menu @@ -57,7 +62,7 @@ #define GEM_KEY_DOWN 3 // Down key is pressed (navigate down through the menu items list, select previous value of the digit/char of editable variable, or next option in select) #define GEM_KEY_LEFT 4 // Left key is pressed (navigate through the Back button to the previous menu page, select previous digit/char of editable variable) #define GEM_KEY_CANCEL 5 // Cancel key is pressed (navigate to the previous (parent) menu page, exit edit mode without saving the variable, exit context loop if allowed within context's settings) -#define GEM_KEY_OK 6 // Ok/Apply key is pressed (toggle boolean menu item, enter edit mode of the associated non-boolean variable, exit edit mode with saving the variable, execute code associated with button) +#define GEM_KEY_OK 6 // Ok/Apply key is pressed (toggle bool menu item, enter edit mode of the associated non-bool variable, exit edit mode with saving the variable, execute code associated with button) // Declaration of Splash type struct Splash { @@ -66,8 +71,8 @@ struct Splash { const uint8_t *image; // Pointer to bitmap image to be shown as splash }; -// Declaration of FontSizeAgfx type -struct FontSizeAgfx { +// Declaration of FontSizeAGFX type +struct FontSizeAGFX { byte width; // Width of the character byte height; // Height of the character byte baselineOffset; // Baseline position relative to the top edge of the character box @@ -79,19 +84,6 @@ struct FontFamiliesAGFX { const GFXfont *small; // Small font family (i.e., 4x6) }; -// Declaration of AppContext type -struct AppContext { - void (*loop)(); // Pointer to loop() function of current context (similar to regular loop() function: if context is defined, executed each regular loop() iteration), - // usually contains code of user-defined action that is run when menu Button is pressed - void (*enter)(); // Pointer to enter() function of current context (similar to regular setup() function, called manually, generally once before context's loop() function, optional), - // usually contains some additional set up required by the user-defined action pointed to by context's loop() - void (*exit)(); // Pointer to exit() function of current context (executed when user exits currently running context, optional), - // usually contains instructions to do some cleanup after context's loop() and to draw menu on screen again, - // if no user-defined function specified, default action will take place that consists of call to reInit(), drawMenu() and clearContext() methods - boolean allowExit = true; // Setting to false will require manually exit the context's loop() from within the loop itself (all necessary key detection should be done in context's loop() accordingly), - // otherwise exit is handled automatically by pressing GEM_KEY_CANCEL key (default is true) -}; - // Forward declaration of necessary classes class GEMItem; @@ -114,99 +106,127 @@ class GEM_adafruit_gfx { default 86 (suitable for 128x64 screen with other variables at their default values) */ GEM_adafruit_gfx(Adafruit_GFX& agfx_, byte menuPointerType_ = GEM_POINTER_ROW, byte menuItemsPerScreen_ = 5, byte menuItemHeight_ = 10, byte menuPageScreenTopOffset_ = 10, byte menuValuesLeftOffset_ = 86); + /* + @param 'agfx_' - reference to an object created with Adafruit GFX library and used for communication with display + @param 'appearance_' - object of type GEMAppearance + */ + GEM_adafruit_gfx(Adafruit_GFX& agfx_, GEMAppearance appearance_); + + /* APPEARANCE OPERATIONS */ + + GEM_adafruit_gfx& setAppearance(GEMAppearance appearance); // Set appearance of the menu (can be overridden in GEMPage on per page basis) + GEMAppearance* getCurrentAppearance(); // Get appearance (as a pointer to GEMAppearance) applied to current menu page (or general if menu page has none of its own) /* INIT OPERATIONS */ - void setSplash(byte width, byte height, const uint8_t *image); // Set custom bitmap image displayed as the splash screen when GEM is being initialized. Should be called before GEM_adafruit_gfx::init(). - // The following is the format of the bitmap as described in Adafruit GFX library documentation. - // A contiguous block of bits, where each '1' bit sets the corresponding pixel to 'color,' while each '0' bit is skipped. - void setSplashDelay(uint16_t value); // Set splash screen delay. Default value 1000ms, max value 65535ms. Setting to 0 will disable splash screen. Should be called before GEM::init(). - void hideVersion(boolean flag = true); // Turn printing of the current GEM library version on splash screen off or back on. Should be called before GEM::init(). - void setForegroundColor(uint16_t color); // Set foreground color of the menu (default is 0xFF) - void setBackgroundColor(uint16_t color); // Set background color of the menu (default is 0x00) - void init(); // Init the menu (load necessary sprites into RAM of the SparkFun Graphic LCD Serial Backpack, display GEM splash screen, etc.) - void reInit(); // Reinitialize the menu (apply GEM specific settings to AltSerialGraphicLCD library) - void setMenuPageCurrent(GEMPage& menuPageCurrent); // Set supplied menu page as current + GEM_adafruit_gfx& setSplash(byte width, byte height, const uint8_t *image); // Set custom bitmap image displayed as the splash screen when GEM is being initialized. Should be called before GEM_adafruit_gfx::init(). + // The following is the format of the bitmap as described in Adafruit GFX library documentation. + // A contiguous block of bits, where each '1' bit sets the corresponding pixel to 'color,' while each '0' bit is skipped. + GEM_adafruit_gfx& setSplashDelay(uint16_t value); // Set splash screen delay. Default value 1000ms, max value 65535ms. Setting to 0 will disable splash screen. Should be called before GEM_adafruit_gfx::init(). + GEM_adafruit_gfx& hideVersion(bool flag = true); // Turn printing of the current GEM library version on splash screen off or back on. Should be called before GEM_adafruit_gfx::init(). + GEM_adafruit_gfx& setTextSize(uint8_t size); // Set text 'magnification' size (as per Adafruit GFX docs); sprites will be scaled maximum up to two times regardless of the supplied value (default is 1) + GEM_adafruit_gfx& setSpriteSize(uint8_t size); // Set sprite scaling factor if it should be different from the 'magnification' size above; sprites will be scaled maximum up to two times regardless of the supplied value (default is 1) + GEM_adafruit_gfx& setFontBig(const GFXfont* font = GEM_FONT_BIG, uint8_t width = 6, uint8_t height = 8, uint8_t baselineOffset = 8); // Set big font + GEM_adafruit_gfx& setFontSmall(const GFXfont* font = GEM_FONT_SMALL, uint8_t width = 4, uint8_t height = 6, uint8_t baselineOffset = 6); // Set small font + GEM_adafruit_gfx& setForegroundColor(uint16_t color); // Set foreground color of the menu (default is 0xFF) + GEM_adafruit_gfx& setBackgroundColor(uint16_t color); // Set background color of the menu (default is 0x00) + GEM_adafruit_gfx& invertKeysDuringEdit(bool invert = true); // Turn inverted order of characters during edit mode on or off + GEM_VIRTUAL GEM_adafruit_gfx& init(); // Init the menu (load necessary sprites into RAM of the SparkFun Graphic LCD Serial Backpack, display GEM splash screen, etc.) + GEM_VIRTUAL GEM_adafruit_gfx& reInit(); // Reinitialize the menu (apply GEM specific settings to AltSerialGraphicLCD library) + GEM_adafruit_gfx& setMenuPageCurrent(GEMPage& menuPageCurrent); // Set supplied menu page as current + GEMPage* getCurrentMenuPage(); // Get pointer to current menu page /* CONTEXT OPERATIONS */ - AppContext context; // Currently set context - void clearContext(); // Clear context + GEMContext context; // Currently set context + GEM_adafruit_gfx& clearContext(); // Clear context /* DRAW OPERATIONS */ - void drawMenu(); // Draw menu on screen, with menu page set earlier in GEM::setMenuPageCurrent() + GEM_VIRTUAL GEM_adafruit_gfx& drawMenu(); // Draw menu on screen, with menu page set earlier in GEM_adafruit_gfx::setMenuPageCurrent() + GEM_adafruit_gfx& setDrawMenuCallback(void (*drawMenuCallback_)()); // Set callback that will be called at the end of GEM_adafruit_gfx::drawMenu() + GEM_adafruit_gfx& removeDrawMenuCallback(); // Remove callback that was called at the end of GEM_adafruit_gfx::drawMenu() + + /* VALUE EDIT */ + + bool isEditMode(); // Checks if menu is in edit mode /* KEY DETECTION */ - boolean readyForKey(); // Check that menu is waiting for the key press - void registerKeyPress(byte keyCode); // Register the key press and trigger corresponding action - // Accepts GEM_KEY_NONE, GEM_KEY_UP, GEM_KEY_RIGHT, GEM_KEY_DOWN, GEM_KEY_LEFT, GEM_KEY_CANCEL, GEM_KEY_OK values - private: + bool readyForKey(); // Checks that menu is waiting for the key press + GEM_adafruit_gfx& registerKeyPress(byte keyCode); // Register the key press and trigger corresponding action + // Accepts GEM_KEY_NONE, GEM_KEY_UP, GEM_KEY_RIGHT, GEM_KEY_DOWN, GEM_KEY_LEFT, GEM_KEY_CANCEL, GEM_KEY_OK values + protected: Adafruit_GFX& _agfx; - byte _menuPointerType; - byte _menuItemsPerScreen; - byte _menuItemHeight; - byte _menuPageScreenTopOffset; - byte _menuValuesLeftOffset; - byte _menuItemFontSize; - FontSizeAgfx _menuItemFont[2] = {{6,12,10},{4,6,6}}; + GEMAppearance* _appearanceCurrent = nullptr; + GEMAppearance _appearance; + byte getMenuItemsPerScreen(); + byte getMenuItemFontSize(); + FontSizeAGFX _menuItemFont[2] = {{6,8,8},{4,6,6}}; FontFamiliesAGFX _fontFamilies = {GEM_FONT_BIG, GEM_FONT_SMALL}; - byte _menuItemInsetOffset; - byte _menuItemTitleLength; - byte _menuItemValueLength; + byte _textSize = 1; + byte _spriteSize = 1; + bool _invertKeysDuringEdit = false; + GEM_VIRTUAL byte getMenuItemTitleLength(); + GEM_VIRTUAL byte getMenuItemValueLength(); Splash _splash; uint16_t _splashDelay = 1000; - boolean _enableVersion = true; + bool _enableVersion = true; uint16_t _menuForegroundColor = 0xFFFF; uint16_t _menuBackgroundColor = 0x0000; /* DRAW OPERATIONS */ GEMPage* _menuPageCurrent = nullptr; - void drawTitleBar(); - void printMenuItemString(const char* str, byte num, byte startPos = 0); - void printMenuItemTitle(const char* str, int offset = 0); - void printMenuItemValue(const char* str, int offset = 0, byte startPos = 0); - void printMenuItemFull(const char* str, int offset = 0); - byte getMenuItemInsetOffset(boolean forSprite = false); - byte getCurrentItemTopOffset(boolean withInsetOffset = true, boolean forSprite = false); - void printMenuItem(GEMItem* menuItemTmp, byte yText, byte yDraw, uint16_t color); - void printMenuItems(); - void drawMenuPointer(boolean clear = false); - void drawScrollbar(); + void (*drawMenuCallback)() = nullptr; + GEM_VIRTUAL void drawTitleBar(); + GEM_VIRTUAL void drawSprite(int16_t x, int16_t y, const Splash sprite[], uint16_t color); + GEM_VIRTUAL void printMenuItemString(const char* str, byte num, byte startPos = 0); + GEM_VIRTUAL void printMenuItemTitle(const char* str, int offset = 0); + GEM_VIRTUAL void printMenuItemValue(const char* str, int offset = 0, byte startPos = 0); + GEM_VIRTUAL void printMenuItemFull(const char* str, int offset = 0); + GEM_VIRTUAL byte getMenuItemInsetOffset(bool forSprite = false); + GEM_VIRTUAL byte getCurrentItemTopOffset(bool withInsetOffset = true, bool forSprite = false); + GEM_VIRTUAL void printMenuItem(GEMItem* menuItemTmp, byte yText, byte yDraw, uint16_t color); + GEM_VIRTUAL void printMenuItems(); + GEM_VIRTUAL void drawMenuPointer(bool clear = false); + GEM_VIRTUAL void drawScrollbar(); /* MENU ITEMS NAVIGATION */ - void nextMenuItem(); - void prevMenuItem(); - void menuItemSelect(); + GEM_VIRTUAL void nextMenuItem(); + GEM_VIRTUAL void prevMenuItem(); + GEM_VIRTUAL void menuItemSelect(); /* VALUE EDIT */ - boolean _editValueMode; + bool _editValueMode; byte _editValueType; byte _editValueLength; byte _editValueCursorPosition; byte _editValueVirtualCursorPosition; char _valueString[GEM_STR_LEN]; int _valueSelectNum; - void enterEditValueMode(); - void checkboxToggle(); - void clearValueVisibleRange(); - void initEditValueCursor(); - void nextEditValueCursorPosition(); - void prevEditValueCursorPosition(); - void drawEditValueCursor(boolean clear = false); - void nextEditValueDigit(); - void prevEditValueDigit(); - void drawEditValueDigit(byte code, boolean clear = false); - void nextEditValueSelect(); - void prevEditValueSelect(); - void drawEditValueSelect(); - void saveEditValue(); - void cancelEditValue(); - void exitEditValue(boolean redrawMenu = true); + GEM_VIRTUAL void enterEditValueMode(); + GEM_VIRTUAL void checkboxToggle(); + GEM_VIRTUAL void clearValueVisibleRange(); + GEM_VIRTUAL void initEditValueCursor(); + GEM_VIRTUAL void nextEditValueCursorPosition(); + GEM_VIRTUAL void prevEditValueCursorPosition(); + GEM_VIRTUAL void drawEditValueCursor(bool clear = false); + GEM_VIRTUAL void nextEditValueDigit(); + GEM_VIRTUAL void prevEditValueDigit(); + GEM_VIRTUAL void drawEditValueDigit(byte code, bool clear = false); + GEM_VIRTUAL void nextEditValueSelect(); + GEM_VIRTUAL void prevEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + GEM_VIRTUAL void nextEditValueSpinner(); + GEM_VIRTUAL void prevEditValueSpinner(); + #endif + GEM_VIRTUAL void drawEditValueSelect(); + GEM_VIRTUAL void saveEditValue(); + GEM_VIRTUAL void cancelEditValue(); + GEM_VIRTUAL void exitEditValue(bool redrawMenu = true); char* trimString(char* str); /* KEY DETECTION */ diff --git a/src/GEM_u8g2.cpp b/src/GEM_u8g2.cpp index 34c3fac..91e1547 100644 --- a/src/GEM_u8g2.cpp +++ b/src/GEM_u8g2.cpp @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -39,7 +39,7 @@ // AVR-based Arduinos have suppoort for dtostrf, some others may require manual inclusion (e.g. SAMD), // see https://github.com/plotly/arduino-api/issues/38#issuecomment-108987647 -#if defined(GEM_SUPPORT_FLOAT_EDIT) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)) +#if defined(GEM_SUPPORT_FLOAT_EDIT) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM) || defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_NRF52840)) #include #endif @@ -50,8 +50,10 @@ #define GEM_CHAR_CODE_DOT 46 #define GEM_CHAR_CODE_SPACE 32 #define GEM_CHAR_CODE_UNDERSCORE 95 -#define GEM_CHAR_CODE_LINE 124 #define GEM_CHAR_CODE_TILDA 126 +#define GEM_CHAR_CODE_BANG 33 +#define GEM_CHAR_CODE_a 97 +#define GEM_CHAR_CODE_ACCENT 96 /* // WIP for Cyrillic values support #define GEM_CHAR_CODE_CYR_YO 1025 @@ -82,6 +84,8 @@ static const unsigned char logo_bits [] U8X8_PROGMEM = { // Sprites of the UI elements used to draw menu +#define sprite_height 8 + #define arrowRight_width 6 #define arrowRight_height 8 static const unsigned char arrowRight_bits [] U8X8_PROGMEM = { @@ -120,14 +124,26 @@ static const unsigned char selectArrows_bits [] U8X8_PROGMEM = { GEM_u8g2::GEM_u8g2(U8G2& u8g2_, byte menuPointerType_, byte menuItemsPerScreen_, byte menuItemHeight_, byte menuPageScreenTopOffset_, byte menuValuesLeftOffset_) : _u8g2(u8g2_) - , _menuPointerType(menuPointerType_) - , _menuItemsPerScreen(menuItemsPerScreen_) - , _menuItemHeight(menuItemHeight_) - , _menuPageScreenTopOffset(menuPageScreenTopOffset_) - , _menuValuesLeftOffset(menuValuesLeftOffset_) { - _menuItemFontSize = _menuItemHeight >= 8 ? 0 : 1; - _menuItemInsetOffset = (_menuItemHeight - _menuItemFont[_menuItemFontSize].height) / 2; + _appearance.menuPointerType = menuPointerType_; + _appearance.menuItemsPerScreen = menuItemsPerScreen_; + _appearance.menuItemHeight = menuItemHeight_; + _appearance.menuPageScreenTopOffset = menuPageScreenTopOffset_; + _appearance.menuValuesLeftOffset = menuValuesLeftOffset_; + _appearanceCurrent = &_appearance; + _splash = {logo_width, logo_height, logo_bits}; + clearContext(); + _editValueMode = false; + _editValueCursorPosition = 0; + memset(_valueString, '\0', GEM_STR_LEN - 1); + _valueSelectNum = -1; +} + +GEM_u8g2::GEM_u8g2(U8G2& u8g2_, GEMAppearance appearance_) + : _u8g2(u8g2_) + , _appearance(appearance_) +{ + _appearanceCurrent = &_appearance; _splash = {logo_width, logo_height, logo_bits}; clearContext(); _editValueMode = false; @@ -136,41 +152,105 @@ GEM_u8g2::GEM_u8g2(U8G2& u8g2_, byte menuPointerType_, byte menuItemsPerScreen_, _valueSelectNum = -1; } +//====================== APPEARANCE OPERATIONS + +GEM_u8g2& GEM_u8g2::setAppearance(GEMAppearance appearance) { + _appearance = appearance; + return *this; +} + +GEMAppearance* GEM_u8g2::getCurrentAppearance() { + return (_menuPageCurrent != nullptr && _menuPageCurrent->_appearance != nullptr) ? _menuPageCurrent->_appearance : &_appearance; +} + +byte GEM_u8g2::getMenuItemsPerScreen() { + return getCurrentAppearance()->menuItemsPerScreen == GEM_ITEMS_COUNT_AUTO ? (_u8g2.getDisplayHeight() - getCurrentAppearance()->menuPageScreenTopOffset) / getCurrentAppearance()->menuItemHeight : getCurrentAppearance()->menuItemsPerScreen; +} + +byte GEM_u8g2::getMenuItemFontSize() { + return getCurrentAppearance()->menuItemHeight >= _menuItemFont[0].height ? 0 : 1; +} + +byte GEM_u8g2::getMenuItemTitleLength() { + return (getCurrentAppearance()->menuValuesLeftOffset - 5) / _menuItemFont[getMenuItemFontSize()].width; +} + +byte GEM_u8g2::getMenuItemValueLength() { + return (_u8g2.getDisplayWidth() - getCurrentAppearance()->menuValuesLeftOffset - 6) / _menuItemFont[getMenuItemFontSize()].width; +} + //====================== INIT OPERATIONS -void GEM_u8g2::setSplash(byte width, byte height, const unsigned char *image) { +GEM_u8g2& GEM_u8g2::setSplash(byte width, byte height, const unsigned char *image) { _splash = {width, height, image}; + return *this; } -void GEM_u8g2::setSplashDelay(uint16_t value) { +GEM_u8g2& GEM_u8g2::setSplashDelay(uint16_t value) { _splashDelay = value; + return *this; } -void GEM_u8g2::hideVersion(boolean flag) { +GEM_u8g2& GEM_u8g2::hideVersion(bool flag) { _enableVersion = !flag; + return *this; } -void GEM_u8g2::enableCyrillic(boolean flag) { - _cyrillicEnabled = flag; - if (_cyrillicEnabled) { - _fontFamilies = {(uint8_t *)GEM_FONT_BIG_CYR, (uint8_t *)GEM_FONT_SMALL_CYR}; +GEM_u8g2& GEM_u8g2::enableUTF8(bool flag) { + _UTF8Enabled = flag; + if (_UTF8Enabled) { _u8g2.enableUTF8Print(); } else { - _fontFamilies = {(uint8_t *)GEM_FONT_BIG, (uint8_t *)GEM_FONT_SMALL}; _u8g2.disableUTF8Print(); } + return *this; +} + +GEM_u8g2& GEM_u8g2::enableCyrillic(bool flag) { + enableUTF8(flag); + if (_UTF8Enabled) { + _fontFamilies = {(uint8_t *)GEM_FONT_BIG_CYR, (uint8_t *)GEM_FONT_SMALL_CYR}; + } else { + _fontFamilies = {(uint8_t *)GEM_FONT_BIG, (uint8_t *)GEM_FONT_SMALL}; + } + _menuItemFont[0] = {6, 8}; + _menuItemFont[1] = {4, 6}; + return *this; } -void GEM_u8g2::init() { +GEM_u8g2& GEM_u8g2::setFontBig(const uint8_t* font, uint8_t width, uint8_t height) { + _fontFamilies.big = font; + _menuItemFont[0] = {width, height}; + return *this; +} + +GEM_u8g2& GEM_u8g2::setFontBig() { + _fontFamilies.big = _UTF8Enabled ? GEM_FONT_BIG_CYR : GEM_FONT_BIG; + _menuItemFont[0] = {6, 8}; + return *this; +} + +GEM_u8g2& GEM_u8g2::setFontSmall(const uint8_t* font, uint8_t width, uint8_t height) { + _fontFamilies.small = font; + _menuItemFont[1] = {width, height}; + return *this; +} + +GEM_u8g2& GEM_u8g2::setFontSmall() { + _fontFamilies.small = _UTF8Enabled ? GEM_FONT_SMALL_CYR : GEM_FONT_SMALL; + _menuItemFont[1] = {4, 6}; + return *this; +} + +GEM_u8g2& GEM_u8g2::invertKeysDuringEdit(bool invert) { + _invertKeysDuringEdit = invert; + return *this; +} + +GEM_u8g2& GEM_u8g2::init() { _u8g2.clear(); _u8g2.setDrawColor(1); _u8g2.setFontPosTop(); - - _menuItemTitleLength = (_menuValuesLeftOffset - 5) / _menuItemFont[_menuItemFontSize].width; - _menuItemValueLength = (_u8g2.getDisplayWidth() - _menuValuesLeftOffset - 6) / _menuItemFont[_menuItemFontSize].width; - if (_menuItemsPerScreen == GEM_ITEMS_COUNT_AUTO) { - _menuItemsPerScreen = (_u8g2.getDisplayHeight() - _menuPageScreenTopOffset) / _menuItemHeight; - } if (_splashDelay > 0) { @@ -203,37 +283,46 @@ void GEM_u8g2::init() { _u8g2.clear(); } + + return *this; } -void GEM_u8g2::reInit() { +GEM_u8g2& GEM_u8g2::reInit() { _u8g2.initDisplay(); _u8g2.setPowerSave(0); _u8g2.clear(); _u8g2.setDrawColor(1); _u8g2.setFontPosTop(); - if (_cyrillicEnabled) { + if (_UTF8Enabled) { _u8g2.enableUTF8Print(); } else { _u8g2.disableUTF8Print(); } + return *this; } -void GEM_u8g2::setMenuPageCurrent(GEMPage& menuPageCurrent) { +GEM_u8g2& GEM_u8g2::setMenuPageCurrent(GEMPage& menuPageCurrent) { _menuPageCurrent = &menuPageCurrent; + return *this; +} + +GEMPage* GEM_u8g2::getCurrentMenuPage() { + return _menuPageCurrent; } //====================== CONTEXT OPERATIONS -void GEM_u8g2::clearContext() { +GEM_u8g2& GEM_u8g2::clearContext() { context.loop = nullptr; context.enter = nullptr; context.exit = nullptr; context.allowExit = true; + return *this; } //====================== DRAW OPERATIONS -void GEM_u8g2::drawMenu() { +GEM_u8g2& GEM_u8g2::drawMenu() { // _u8g2.clear(); // Not clearing for better performance _u8g2.firstPage(); do { @@ -241,34 +330,48 @@ void GEM_u8g2::drawMenu() { printMenuItems(); drawMenuPointer(); drawScrollbar(); + if (drawMenuCallback != nullptr) { + drawMenuCallback(); + } } while (_u8g2.nextPage()); + return *this; +} + +GEM_u8g2& GEM_u8g2::setDrawMenuCallback(void (*drawMenuCallback_)()) { + drawMenuCallback = drawMenuCallback_; + return *this; +} + +GEM_u8g2& GEM_u8g2::removeDrawMenuCallback() { + drawMenuCallback = nullptr; + return *this; } void GEM_u8g2::drawTitleBar() { _u8g2.setFont(_fontFamilies.small); _u8g2.setCursor(5, 0); _u8g2.print(_menuPageCurrent->title); - _u8g2.setFont(_menuItemFontSize ? _fontFamilies.small : _fontFamilies.big); + _u8g2.setFont(getMenuItemFontSize() ? _fontFamilies.small : _fontFamilies.big); } void GEM_u8g2::printMenuItemString(const char* str, byte num, byte startPos) { - if (_cyrillicEnabled) { + if (_UTF8Enabled) { byte j = 0; byte p = 0; - while (j < startPos && str[p] != '\0') { - if ((byte)str[p] != 208 && (byte)str[p] != 209) { + while ((j < startPos || ((byte)str[p] >= 128 && (byte)str[p] <= 191)) && str[p] != '\0') { + if ((byte)str[p] <= 127 || (byte)str[p] >= 194) { j++; } p++; } byte startPosReal = p; - byte i = startPosReal; + byte i = j; byte k = startPosReal; - while (i < num + startPosReal && str[k] != '\0') { + while ((i < num + j || ((byte)str[k] >= 128 && (byte)str[k] <= 191)) && str[k] != '\0') { _u8g2.print(str[k]); - if ((byte)str[k] != 208 && (byte)str[k] != 209) { + if ((byte)str[k] <= 127 || (byte)str[k] >= 194) { i++; } k++; @@ -286,116 +389,170 @@ void GEM_u8g2::printMenuItemString(const char* str, byte num, byte startPos) { } void GEM_u8g2::printMenuItemTitle(const char* str, int offset) { - printMenuItemString(str, _menuItemTitleLength + offset); + printMenuItemString(str, getMenuItemTitleLength() + offset); } void GEM_u8g2::printMenuItemValue(const char* str, int offset, byte startPos) { - printMenuItemString(str, _menuItemValueLength + offset, startPos); + printMenuItemString(str, getMenuItemValueLength() + offset, startPos); } void GEM_u8g2::printMenuItemFull(const char* str, int offset) { - printMenuItemString(str, _menuItemTitleLength + _menuItemValueLength + offset); + printMenuItemString(str, getMenuItemTitleLength() + getMenuItemValueLength() + offset); } -byte GEM_u8g2::getMenuItemInsetOffset(boolean forSprite) { - return _menuItemInsetOffset + (forSprite ? (_menuItemFontSize ? -1 : 0) : -1 ); // With additional offset for 6x8 sprites to compensate for smaller font size +byte GEM_u8g2::getMenuItemInsetOffset(bool forSprite) { + byte menuItemFontSize = getMenuItemFontSize(); + byte menuItemInsetOffset = (getCurrentAppearance()->menuItemHeight - _menuItemFont[menuItemFontSize].height) / 2; + return menuItemInsetOffset + (forSprite ? (_menuItemFont[menuItemFontSize].height - sprite_height) / 2 : -1); // With additional offset for sprites and text for better visual alignment } -byte GEM_u8g2::getCurrentItemTopOffset(boolean withInsetOffset, boolean forSprite) { - return (_menuPageCurrent->currentItemNum % _menuItemsPerScreen) * _menuItemHeight + _menuPageScreenTopOffset + (withInsetOffset ? getMenuItemInsetOffset(forSprite) : 0); +byte GEM_u8g2::getCurrentItemTopOffset(bool withInsetOffset, bool forSprite) { + return (_menuPageCurrent->currentItemNum % getMenuItemsPerScreen()) * getCurrentAppearance()->menuItemHeight + getCurrentAppearance()->menuPageScreenTopOffset + (withInsetOffset ? getMenuItemInsetOffset(forSprite) : 0); } void GEM_u8g2::printMenuItems() { - byte currentPageScreenNum = _menuPageCurrent->currentItemNum / _menuItemsPerScreen; - GEMItem* menuItemTmp = _menuPageCurrent->getMenuItem(currentPageScreenNum * _menuItemsPerScreen); - byte y = _menuPageScreenTopOffset; + byte menuItemsPerScreen = getMenuItemsPerScreen(); + byte currentPageScreenNum = _menuPageCurrent->currentItemNum / menuItemsPerScreen; + GEMItem* menuItemTmp = _menuPageCurrent->getMenuItem(currentPageScreenNum * menuItemsPerScreen); + byte y = getCurrentAppearance()->menuPageScreenTopOffset; byte i = 0; char valueStringTmp[GEM_STR_LEN]; - while (menuItemTmp != nullptr && i < _menuItemsPerScreen) { + while (menuItemTmp != nullptr && i < menuItemsPerScreen) { byte yText = y + getMenuItemInsetOffset(); byte yDraw = y + getMenuItemInsetOffset(true); switch (menuItemTmp->type) { case GEM_ITEM_VAL: - _u8g2.setCursor(5, yText); - if (menuItemTmp->readonly) { - printMenuItemTitle(menuItemTmp->title, -1); - _u8g2.print("^"); - } else { - printMenuItemTitle(menuItemTmp->title); - } - - _u8g2.setCursor(_menuValuesLeftOffset, yText); - switch (menuItemTmp->linkedType) { - case GEM_VAL_INTEGER: - if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { - printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); - drawEditValueCursor(); - } else { - itoa(*(int*)menuItemTmp->linkedVariable, valueStringTmp, 10); - printMenuItemValue(valueStringTmp); - } - break; - case GEM_VAL_BYTE: - if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { - printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); - drawEditValueCursor(); - } else { - itoa(*(byte*)menuItemTmp->linkedVariable, valueStringTmp, 10); - printMenuItemValue(valueStringTmp); - } - break; - case GEM_VAL_CHAR: - if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { - printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); - drawEditValueCursor(); - } else { - printMenuItemValue((char*)menuItemTmp->linkedVariable); - } - break; - case GEM_VAL_BOOLEAN: - if (*(boolean*)menuItemTmp->linkedVariable) { - _u8g2.drawXBMP(_menuValuesLeftOffset, yDraw, checkboxChecked_width, checkboxChecked_height, checkboxChecked_bits); - } else { - _u8g2.drawXBMP(_menuValuesLeftOffset, yDraw, checkboxUnchecked_width, checkboxUnchecked_height, checkboxUnchecked_bits); - } - break; - case GEM_VAL_SELECT: - { - GEMSelect* select = menuItemTmp->select; + { + _u8g2.setCursor(5, yText); + if (menuItemTmp->readonly) { + printMenuItemTitle(menuItemTmp->title, -1); + _u8g2.print("^"); + } else { + printMenuItemTitle(menuItemTmp->title); + } + + byte menuValuesLeftOffset = getCurrentAppearance()->menuValuesLeftOffset; + _u8g2.setCursor(menuValuesLeftOffset, yText); + switch (menuItemTmp->linkedType) { + case GEM_VAL_INTEGER: if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { - printMenuItemValue(select->getOptionNameByIndex(_valueSelectNum)); - _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 7, yDraw, selectArrows_width, selectArrows_height, selectArrows_bits); + printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); drawEditValueCursor(); } else { - printMenuItemValue(select->getSelectedOptionName(menuItemTmp->linkedVariable)); - _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 7, yDraw, selectArrows_width, selectArrows_height, selectArrows_bits); + itoa(*(int*)menuItemTmp->linkedVariable, valueStringTmp, 10); + printMenuItemValue(valueStringTmp); } - } - break; - #ifdef GEM_SUPPORT_FLOAT_EDIT - case GEM_VAL_FLOAT: - if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { - printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); - drawEditValueCursor(); - } else { - // sprintf(valueStringTmp,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards - dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - printMenuItemValue(valueStringTmp); - } - break; - case GEM_VAL_DOUBLE: - if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { - printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); - drawEditValueCursor(); - } else { - // sprintf(valueStringTmp,"%.6f", *(double*)menuItemTmp->linkedVariable); // May work for non-AVR boards - dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - printMenuItemValue(valueStringTmp); - } - break; - #endif + break; + case GEM_VAL_BYTE: + if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { + printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); + drawEditValueCursor(); + } else { + itoa(*(byte*)menuItemTmp->linkedVariable, valueStringTmp, 10); + printMenuItemValue(valueStringTmp); + } + break; + case GEM_VAL_CHAR: + if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { + printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); + drawEditValueCursor(); + } else { + printMenuItemValue((char*)menuItemTmp->linkedVariable); + } + break; + case GEM_VAL_BOOL: + if (*(bool*)menuItemTmp->linkedVariable) { + _u8g2.drawXBMP(menuValuesLeftOffset, yDraw, checkboxChecked_width, checkboxChecked_height, checkboxChecked_bits); + } else { + _u8g2.drawXBMP(menuValuesLeftOffset, yDraw, checkboxUnchecked_width, checkboxUnchecked_height, checkboxUnchecked_bits); + } + break; + case GEM_VAL_SELECT: + { + GEMSelect* select = menuItemTmp->select; + if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { + printMenuItemValue(select->getOptionNameByIndex(_valueSelectNum)); + _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 7, yDraw, selectArrows_width, selectArrows_height, selectArrows_bits); + drawEditValueCursor(); + } else { + printMenuItemValue(select->getSelectedOptionName(menuItemTmp->linkedVariable)); + _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 7, yDraw, selectArrows_width, selectArrows_height, selectArrows_bits); + } + } + break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { + GEMSpinnerValue valueTmp = spinner->getOptionNameByIndex(menuItemTmp->linkedVariable, _valueSelectNum); + switch (spinner->getType()) { + case GEM_VAL_BYTE: + itoa(valueTmp.valByte, valueStringTmp, 10); + break; + case GEM_VAL_INTEGER: + itoa(valueTmp.valInt, valueStringTmp, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(valueTmp.valFloat, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + case GEM_VAL_DOUBLE: + dtostrf(valueTmp.valDouble, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + #endif + } + printMenuItemValue(valueStringTmp); + _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 7, yDraw, selectArrows_width, selectArrows_height, selectArrows_bits); + drawEditValueCursor(); + } else { + switch (spinner->getType()) { + case GEM_VAL_BYTE: + itoa(*(byte*)menuItemTmp->linkedVariable, valueStringTmp, 10); + break; + case GEM_VAL_INTEGER: + itoa(*(int*)menuItemTmp->linkedVariable, valueStringTmp, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + case GEM_VAL_DOUBLE: + dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + break; + #endif + } + printMenuItemValue(valueStringTmp); + _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 7, yDraw, selectArrows_width, selectArrows_height, selectArrows_bits); + } + } + break; + #endif + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { + printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); + drawEditValueCursor(); + } else { + // sprintf(valueStringTmp,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards + dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + printMenuItemValue(valueStringTmp); + } + break; + case GEM_VAL_DOUBLE: + if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { + printMenuItemValue(_valueString, 0, _editValueVirtualCursorPosition - _editValueCursorPosition); + drawEditValueCursor(); + } else { + // sprintf(valueStringTmp,"%.6f", *(double*)menuItemTmp->linkedVariable); // May work for non-AVR boards + dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); + printMenuItemValue(valueStringTmp); + } + break; + #endif + } + break; } - break; case GEM_ITEM_LINK: _u8g2.setCursor(5, yText); if (menuItemTmp->readonly) { @@ -407,7 +564,6 @@ void GEM_u8g2::printMenuItems() { _u8g2.drawXBMP(_u8g2.getDisplayWidth() - 8, yDraw, arrowRight_width, arrowRight_height, arrowRight_bits); break; case GEM_ITEM_BACK: - _u8g2.setCursor(11, yText); _u8g2.drawXBMP(5, yDraw, arrowLeft_width, arrowLeft_height, arrowLeft_bits); break; case GEM_ITEM_BUTTON: @@ -422,7 +578,7 @@ void GEM_u8g2::printMenuItems() { break; } menuItemTmp = menuItemTmp->getMenuItemNext(); - y += _menuItemHeight; + y += getCurrentAppearance()->menuItemHeight; i++; } memset(valueStringTmp, '\0', GEM_STR_LEN - 1); @@ -432,22 +588,23 @@ void GEM_u8g2::drawMenuPointer() { if (_menuPageCurrent->itemsCount > 0) { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); int pointerPosition = getCurrentItemTopOffset(false); - if (_menuPointerType == GEM_POINTER_DASH) { + byte menuItemHeight = getCurrentAppearance()->menuItemHeight; + if (getCurrentAppearance()->menuPointerType == GEM_POINTER_DASH) { if (menuItemTmp->readonly) { - for (byte i = 0; i < (_menuItemHeight - 1) / 2; i++) { + for (byte i = 0; i < (menuItemHeight - 1) / 2; i++) { _u8g2.drawPixel(0, pointerPosition + i * 2); _u8g2.drawPixel(1, pointerPosition + i * 2 + 1); } } else { - _u8g2.drawBox(0, pointerPosition, 2, _menuItemHeight - 1); + _u8g2.drawBox(0, pointerPosition, 2, menuItemHeight - 1); } } else if (!_editValueMode) { _u8g2.setDrawColor(2); - _u8g2.drawBox(0, pointerPosition - 1, _u8g2.getDisplayWidth() - 2, _menuItemHeight + 1); + _u8g2.drawBox(0, pointerPosition - 1, _u8g2.getDisplayWidth() - 2, menuItemHeight + 1); _u8g2.setDrawColor(1); if (menuItemTmp->readonly) { _u8g2.setDrawColor(0); - for (byte i = 0; i < (_menuItemHeight + 2) / 2; i++) { + for (byte i = 0; i < (menuItemHeight + 2) / 2; i++) { _u8g2.drawPixel(0, pointerPosition + i * 2); _u8g2.drawPixel(1, pointerPosition + i * 2 - 1); } @@ -458,11 +615,13 @@ void GEM_u8g2::drawMenuPointer() { } void GEM_u8g2::drawScrollbar() { - byte screensCount = (_menuPageCurrent->itemsCount % _menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / _menuItemsPerScreen : _menuPageCurrent->itemsCount / _menuItemsPerScreen + 1; + byte menuItemsPerScreen = getMenuItemsPerScreen(); + byte screensCount = (_menuPageCurrent->itemsCount % menuItemsPerScreen == 0) ? _menuPageCurrent->itemsCount / menuItemsPerScreen : _menuPageCurrent->itemsCount / menuItemsPerScreen + 1; if (screensCount > 1) { - byte currentScreenNum = _menuPageCurrent->currentItemNum / _menuItemsPerScreen; - byte scrollbarHeight = (_u8g2.getDisplayHeight() - _menuPageScreenTopOffset + 1) / screensCount; - byte scrollbarPosition = currentScreenNum * scrollbarHeight + _menuPageScreenTopOffset - 1; + byte currentScreenNum = _menuPageCurrent->currentItemNum / menuItemsPerScreen; + byte menuPageScreenTopOffset = getCurrentAppearance()->menuPageScreenTopOffset; + byte scrollbarHeight = (_u8g2.getDisplayHeight() - menuPageScreenTopOffset + 1) / screensCount; + byte scrollbarPosition = currentScreenNum * scrollbarHeight + menuPageScreenTopOffset - 1; _u8g2.drawLine(_u8g2.getDisplayWidth() - 1, scrollbarPosition, _u8g2.getDisplayWidth() - 1, scrollbarPosition + scrollbarHeight); } } @@ -541,7 +700,7 @@ void GEM_u8g2::enterEditValueMode() { _editValueLength = GEM_STR_LEN - 1; initEditValueCursor(); break; - case GEM_VAL_BOOLEAN: + case GEM_VAL_BOOL: checkboxToggle(); drawMenu(); break; @@ -552,6 +711,15 @@ void GEM_u8g2::enterEditValueMode() { initEditValueCursor(); } break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + _valueSelectNum = spinner->getSelectedOptionNum(menuItemTmp->linkedVariable); + initEditValueCursor(); + } + break; + #endif #ifdef GEM_SUPPORT_FLOAT_EDIT case GEM_VAL_FLOAT: // sprintf(_valueString,"%.6f", *(float*)menuItemTmp->linkedVariable); // May work for non-AVR boards @@ -571,9 +739,8 @@ void GEM_u8g2::enterEditValueMode() { void GEM_u8g2::checkboxToggle() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - int topOffset = getCurrentItemTopOffset(true, true); - boolean checkboxValue = *(boolean*)menuItemTmp->linkedVariable; - *(boolean*)menuItemTmp->linkedVariable = !checkboxValue; + bool checkboxValue = *(bool*)menuItemTmp->linkedVariable; + *(bool*)menuItemTmp->linkedVariable = !checkboxValue; if (menuItemTmp->callbackAction != nullptr) { if (menuItemTmp->callbackWithArgs) { menuItemTmp->callbackActionArg(menuItemTmp->callbackData); @@ -593,7 +760,7 @@ void GEM_u8g2::initEditValueCursor() { } void GEM_u8g2::nextEditValueCursorPosition() { - if ((_editValueCursorPosition != _menuItemValueLength - 1) && (_editValueCursorPosition != _editValueLength - 1) && (_valueString[_editValueCursorPosition] != '\0')) { + if ((_editValueCursorPosition != getMenuItemValueLength() - 1) && (_editValueCursorPosition != _editValueLength - 1) && (_valueString[_editValueCursorPosition] != '\0')) { _editValueCursorPosition++; } if ((_editValueVirtualCursorPosition != _editValueLength - 1) && (_valueString[_editValueVirtualCursorPosition] != '\0')) { @@ -614,51 +781,73 @@ void GEM_u8g2::prevEditValueCursorPosition() { void GEM_u8g2::drawEditValueCursor() { int pointerPosition = getCurrentItemTopOffset(false); - byte cursorLeftOffset = _menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[_menuItemFontSize].width; + byte menuItemFontSize = getMenuItemFontSize(); + byte cursorLeftOffset = getCurrentAppearance()->menuValuesLeftOffset + _editValueCursorPosition * _menuItemFont[menuItemFontSize].width; _u8g2.setDrawColor(2); - if (_editValueType == GEM_VAL_SELECT) { - _u8g2.drawBox(cursorLeftOffset - 1, pointerPosition - 1, _u8g2.getDisplayWidth() - cursorLeftOffset - 1, _menuItemHeight + 1); + if (_editValueType == GEM_VAL_SELECT || _editValueType == GEM_VAL_SPINNER) { + _u8g2.drawBox(cursorLeftOffset - 1, pointerPosition - 1, _u8g2.getDisplayWidth() - cursorLeftOffset - 1, getCurrentAppearance()->menuItemHeight + 1); } else { - _u8g2.drawBox(cursorLeftOffset - 1, pointerPosition - 1, _menuItemFont[_menuItemFontSize].width + 1, _menuItemHeight + 1); + _u8g2.drawBox(cursorLeftOffset - 1, pointerPosition - 1, _menuItemFont[menuItemFontSize].width + 1, getCurrentAppearance()->menuItemHeight + 1); } _u8g2.setDrawColor(1); } void GEM_u8g2::nextEditValueDigit() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); char chr = _valueString[_editValueVirtualCursorPosition]; byte code = (byte)chr; if (_editValueType == GEM_VAL_CHAR) { - switch (code) { - case 0: - code = GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_TILDA: - code = GEM_CHAR_CODE_SPACE; - break; - /* - // WIP for Cyrillic values support - case GEM_CHAR_CODE_TILDA: - code = _cyrillicEnabled ? GEM_CHAR_CODE_CYR_A : GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_CYR_YA_SM: - code = GEM_CHAR_CODE_SPACE; - break; - case GEM_CHAR_CODE_CYR_E: - code = GEM_CHAR_CODE_CYR_YO; - break; - case GEM_CHAR_CODE_CYR_YO: - code = GEM_CHAR_CODE_CYR_E + 1; - break; - case GEM_CHAR_CODE_CYR_E_SM: - code = GEM_CHAR_CODE_CYR_YO_SM; - break; - case GEM_CHAR_CODE_CYR_YO_SM: - code = GEM_CHAR_CODE_CYR_E_SM + 1; - break; - */ - default: - code++; - break; + if (menuItemTmp->adjustedAsciiOrder) { + switch (code) { + case 0: + code = GEM_CHAR_CODE_a; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_a; + break; + case GEM_CHAR_CODE_ACCENT: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_TILDA: + code = GEM_CHAR_CODE_BANG; + break; + default: + code++; + break; + } + } else { + switch (code) { + case 0: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_TILDA: + code = GEM_CHAR_CODE_SPACE; + break; + /* + // WIP for Cyrillic values support + case GEM_CHAR_CODE_TILDA: + code = _cyrillicEnabled ? GEM_CHAR_CODE_CYR_A : GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_CYR_YA_SM: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_CYR_E: + code = GEM_CHAR_CODE_CYR_YO; + break; + case GEM_CHAR_CODE_CYR_YO: + code = GEM_CHAR_CODE_CYR_E + 1; + break; + case GEM_CHAR_CODE_CYR_E_SM: + code = GEM_CHAR_CODE_CYR_YO_SM; + break; + case GEM_CHAR_CODE_CYR_YO_SM: + code = GEM_CHAR_CODE_CYR_E_SM + 1; + break; + */ + default: + code++; + break; + } } } else { switch (code) { @@ -686,43 +875,64 @@ void GEM_u8g2::nextEditValueDigit() { } void GEM_u8g2::prevEditValueDigit() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); char chr = _valueString[_editValueVirtualCursorPosition]; byte code = (byte)chr; if (_editValueType == GEM_VAL_CHAR) { - switch (code) { - case 0: - code = GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_SPACE: - code = GEM_CHAR_CODE_TILDA; - break; - /* - // WIP for Cyrillic values support - case 0: - code = _cyrillicEnabled ? GEM_CHAR_CODE_CYR_YA_SM : GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_SPACE: - code = _cyrillicEnabled ? GEM_CHAR_CODE_CYR_YA_SM : GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_CYR_A: - code = GEM_CHAR_CODE_TILDA; - break; - case GEM_CHAR_CODE_CYR_E + 1: - code = GEM_CHAR_CODE_CYR_YO; - break; - case GEM_CHAR_CODE_CYR_YO: - code = GEM_CHAR_CODE_CYR_E; - break; - case GEM_CHAR_CODE_CYR_E_SM + 1: - code = GEM_CHAR_CODE_CYR_YO_SM; - break; - case GEM_CHAR_CODE_CYR_YO_SM: - code = GEM_CHAR_CODE_CYR_E_SM; - break; - */ - default: - code--; - break; + if (menuItemTmp->adjustedAsciiOrder) { + switch (code) { + case 0: + code = GEM_CHAR_CODE_ACCENT; + break; + case GEM_CHAR_CODE_BANG: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_a: + code = GEM_CHAR_CODE_SPACE; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_ACCENT; + break; + default: + code--; + break; + } + } else { + switch (code) { + case 0: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_SPACE: + code = GEM_CHAR_CODE_TILDA; + break; + /* + // WIP for Cyrillic values support + case 0: + code = _cyrillicEnabled ? GEM_CHAR_CODE_CYR_YA_SM : GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_SPACE: + code = _cyrillicEnabled ? GEM_CHAR_CODE_CYR_YA_SM : GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_CYR_A: + code = GEM_CHAR_CODE_TILDA; + break; + case GEM_CHAR_CODE_CYR_E + 1: + code = GEM_CHAR_CODE_CYR_YO; + break; + case GEM_CHAR_CODE_CYR_YO: + code = GEM_CHAR_CODE_CYR_E; + break; + case GEM_CHAR_CODE_CYR_E_SM + 1: + code = GEM_CHAR_CODE_CYR_YO_SM; + break; + case GEM_CHAR_CODE_CYR_YO_SM: + code = GEM_CHAR_CODE_CYR_E_SM; + break; + */ + default: + code--; + break; + } } } else { switch (code) { @@ -765,17 +975,29 @@ void GEM_u8g2::nextEditValueSelect() { } void GEM_u8g2::prevEditValueSelect() { - GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - GEMSelect* select = menuItemTmp->select; if (_valueSelectNum > 0) { _valueSelectNum--; } drawMenu(); } +#ifdef GEM_SUPPORT_SPINNER +void GEM_u8g2::nextEditValueSpinner() { + GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); + GEMSpinner* spinner = menuItemTmp->spinner; + if (_valueSelectNum+1 < spinner->getLength()) { + _valueSelectNum++; + } + drawMenu(); +} + +void GEM_u8g2::prevEditValueSpinner() { + prevEditValueSelect(); +} +#endif + void GEM_u8g2::saveEditValue() { GEMItem* menuItemTmp = _menuPageCurrent->getCurrentMenuItem(); - void* temp; switch (menuItemTmp->linkedType) { case GEM_VAL_INTEGER: *(int*)menuItemTmp->linkedVariable = atoi(_valueString); @@ -792,6 +1014,14 @@ void GEM_u8g2::saveEditValue() { select->setValue(menuItemTmp->linkedVariable, _valueSelectNum); } break; + #ifdef GEM_SUPPORT_SPINNER + case GEM_VAL_SPINNER: + { + GEMSpinner* spinner = menuItemTmp->spinner; + spinner->setValue(menuItemTmp->linkedVariable, _valueSelectNum); + } + break; + #endif #ifdef GEM_SUPPORT_FLOAT_EDIT case GEM_VAL_FLOAT: *(float*)menuItemTmp->linkedVariable = atof(_valueString); @@ -823,6 +1053,10 @@ void GEM_u8g2::exitEditValue() { drawMenu(); } +bool GEM_u8g2::isEditMode() { + return _editValueMode; +} + // Trim leading/trailing whitespaces // Author: Adam Rosenfield, https://stackoverflow.com/a/122721 char* GEM_u8g2::trimString(char* str) { @@ -846,7 +1080,7 @@ char* GEM_u8g2::trimString(char* str) { //====================== KEY DETECTION -boolean GEM_u8g2::readyForKey() { +bool GEM_u8g2::readyForKey() { if ( (context.loop == nullptr) || ((context.loop != nullptr) && (context.allowExit)) ) { return true; @@ -857,9 +1091,10 @@ boolean GEM_u8g2::readyForKey() { } -void GEM_u8g2::registerKeyPress(byte keyCode) { +GEM_u8g2& GEM_u8g2::registerKeyPress(byte keyCode) { _currentKey = keyCode; dispatchKeyPress(); + return *this; } void GEM_u8g2::dispatchKeyPress() { @@ -883,24 +1118,36 @@ void GEM_u8g2::dispatchKeyPress() { case GEM_KEY_UP: if (_editValueType == GEM_VAL_SELECT) { prevEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + } else if (_editValueType == GEM_VAL_SPINNER) { + prevEditValueSpinner(); + #endif + } else if (_invertKeysDuringEdit) { + prevEditValueDigit(); } else { nextEditValueDigit(); } break; case GEM_KEY_RIGHT: - if (_editValueType != GEM_VAL_SELECT) { + if (_editValueType != GEM_VAL_SELECT && _editValueType != GEM_VAL_SPINNER) { nextEditValueCursorPosition(); } break; case GEM_KEY_DOWN: if (_editValueType == GEM_VAL_SELECT) { nextEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + } else if (_editValueType == GEM_VAL_SPINNER) { + nextEditValueSpinner(); + #endif + } else if (_invertKeysDuringEdit) { + nextEditValueDigit(); } else { prevEditValueDigit(); } break; case GEM_KEY_LEFT: - if (_editValueType != GEM_VAL_SELECT) { + if (_editValueType != GEM_VAL_SELECT && _editValueType != GEM_VAL_SPINNER) { prevEditValueCursorPosition(); } break; diff --git a/src/GEM_u8g2.h b/src/GEM_u8g2.h index 4ffd204..02e32a0 100644 --- a/src/GEM_u8g2.h +++ b/src/GEM_u8g2.h @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -40,8 +40,13 @@ #ifdef GEM_ENABLE_U8G2_VERSION #include +#include "GEMAppearance.h" +#include "GEMContext.h" #include "GEMPage.h" #include "GEMSelect.h" +#ifdef GEM_SUPPORT_SPINNER +#include "GEMSpinner.h" +#endif #include "constants.h" // Macro constants (aliases) for u8g2 font families used to draw menu @@ -57,7 +62,7 @@ #define GEM_KEY_DOWN U8X8_MSG_GPIO_MENU_DOWN // Down key is pressed (navigate down through the menu items list, select previous value of the digit/char of editable variable, or next option in select) #define GEM_KEY_LEFT U8X8_MSG_GPIO_MENU_PREV // Left key is pressed (navigate through the Back button to the previous menu page, select previous digit/char of editable variable) #define GEM_KEY_CANCEL U8X8_MSG_GPIO_MENU_HOME // Cancel key is pressed (navigate to the previous (parent) menu page, exit edit mode without saving the variable, exit context loop if allowed within context's settings) -#define GEM_KEY_OK U8X8_MSG_GPIO_MENU_SELECT // Ok/Apply key is pressed (toggle boolean menu item, enter edit mode of the associated non-boolean variable, exit edit mode with saving the variable, execute code associated with button) +#define GEM_KEY_OK U8X8_MSG_GPIO_MENU_SELECT // Ok/Apply key is pressed (toggle bool menu item, enter edit mode of the associated non-bool variable, exit edit mode with saving the variable, execute code associated with button) // Declaration of Splash type struct Splash { @@ -78,19 +83,6 @@ struct FontFamiliesU8g2 { const uint8_t *small; // Small font family (i.e., 4x6) }; -// Declaration of AppContext type -struct AppContext { - void (*loop)(); // Pointer to loop() function of current context (similar to regular loop() function: if context is defined, executed each regular loop() iteration), - // usually contains code of user-defined action that is run when menu Button is pressed - void (*enter)(); // Pointer to enter() function of current context (similar to regular setup() function, called manually, generally once before context's loop() function, optional), - // usually contains some additional set up required by the user-defined action pointed to by context's loop() - void (*exit)(); // Pointer to exit() function of current context (executed when user exits currently running context, optional), - // usually contains instructions to do some cleanup after context's loop() and to draw menu on screen again, - // if no user-defined function specified, default action will take place that consists of call to reInit(), drawMenu() and clearContext() methods - boolean allowExit = true; // Setting to false will require manually exit the context's loop() from within the loop itself (all necessary key detection should be done in context's loop() accordingly), - // otherwise exit is handled automatically by pressing GEM_KEY_CANCEL key (default is true) -}; - // Forward declaration of necessary classes class GEMItem; @@ -113,93 +105,118 @@ class GEM_u8g2 { default 86 (suitable for 128x64 screen with other variables at their default values) */ GEM_u8g2(U8G2& u8g2_, byte menuPointerType_ = GEM_POINTER_ROW, byte menuItemsPerScreen_ = 5, byte menuItemHeight_ = 10, byte menuPageScreenTopOffset_ = 10, byte menuValuesLeftOffset_ = 86); + /* + @param 'u8g2_' - reference to an object created with U8g2 library and used for communication with LCD + @param 'appearance_' - object of type GEMAppearance + */ + GEM_u8g2(U8G2& u8g2_, GEMAppearance appearance_); + /* APPEARANCE OPERATIONS */ + + GEM_u8g2& setAppearance(GEMAppearance appearance); // Set appearance of the menu (can be overridden in GEMPage on per page basis) + GEMAppearance* getCurrentAppearance(); // Get appearance (as a pointer to GEMAppearance) applied to current menu page (or general if menu page has none of its own) + /* INIT OPERATIONS */ - void setSplash(byte width, byte height, const unsigned char *image); // Set custom XBM image displayed as the splash screen when GEM is being initialized. Should be called before GEM_u8g2::init(). - void setSplashDelay(uint16_t value); // Set splash screen delay. Default value 1000ms, max value 65535ms. Setting to 0 will disable splash screen. Should be called before GEM_u8g2::init(). - void hideVersion(boolean flag = true); // Turn printing of the current GEM library version on splash screen off or back on. Should be called before GEM_u8g2::init(). - void enableCyrillic(boolean flag = true); // Enable Cyrillic set of fonts. Generally should be called before GEM_u8g2::init(). To revert to non-Cyrillic fonts pass false: enableCyrillic(false). - void init(); // Init the menu (set necessary settings, display GEM splash screen, etc.) - void reInit(); // Reinitialize the menu (call U8g2::initDisplay() and then reapply GEM specific settings) - void setMenuPageCurrent(GEMPage& menuPageCurrent); // Set supplied menu page as current + GEM_u8g2& setSplash(byte width, byte height, const unsigned char *image); // Set custom XBM image displayed as the splash screen when GEM is being initialized. Should be called before GEM_u8g2::init(). + GEM_u8g2& setSplashDelay(uint16_t value); // Set splash screen delay. Default value 1000ms, max value 65535ms. Setting to 0 will disable splash screen. Should be called before GEM_u8g2::init(). + GEM_u8g2& hideVersion(bool flag = true); // Turn printing of the current GEM library version on splash screen off or back on. Should be called before GEM_u8g2::init(). + GEM_u8g2& enableUTF8(bool flag = true); // Enable UTF8 fonts support. Generally should be called before GEM_u8g2::init(). To disable UTF8 fonts support pass false: enableUTF8(false). + GEM_u8g2& enableCyrillic(bool flag = true); // Enable default Cyrillic set of fonts with GEM. Generally should be called before GEM_u8g2::init(). To revert to non-Cyrillic fonts pass false: enableCyrillic(false). + GEM_u8g2& setFontBig(const uint8_t* font, uint8_t width = 6, uint8_t height = 8); // Set big font + GEM_u8g2& setFontBig(); // Revert big font to default value (with respect to _UTF8Enabled flag) + GEM_u8g2& setFontSmall(const uint8_t* font, uint8_t width = 4, uint8_t height = 6); // Set small font + GEM_u8g2& setFontSmall(); // Revert small font to default value (with respect to _UTF8Enabled flag) + GEM_u8g2& invertKeysDuringEdit(bool invert = true); // Turn inverted order of characters during edit mode on or off + GEM_VIRTUAL GEM_u8g2& init(); // Init the menu (set necessary settings, display GEM splash screen, etc.) + GEM_VIRTUAL GEM_u8g2& reInit(); // Reinitialize the menu (call U8g2::initDisplay() and then reapply GEM specific settings) + GEM_u8g2& setMenuPageCurrent(GEMPage& menuPageCurrent); // Set supplied menu page as current + GEMPage* getCurrentMenuPage(); // Get pointer to current menu page /* CONTEXT OPERATIONS */ - AppContext context; // Currently set context - void clearContext(); // Clear context + GEMContext context; // Currently set context + GEM_u8g2& clearContext(); // Clear context /* DRAW OPERATIONS */ - void drawMenu(); // Draw menu on screen, with menu page set earlier in GEM_u8g2::setMenuPageCurrent() + GEM_VIRTUAL GEM_u8g2& drawMenu(); // Draw menu on screen, with menu page set earlier in GEM_u8g2::setMenuPageCurrent() + GEM_u8g2& setDrawMenuCallback(void (*drawMenuCallback_)()); // Set callback that will be called at the end of GEM_u8g2::drawMenu() + GEM_u8g2& removeDrawMenuCallback(); // Remove callback that was called at the end of GEM_u8g2::drawMenu() + + /* VALUE EDIT */ + + bool isEditMode(); // Checks if menu is in edit mode /* KEY DETECTION */ - boolean readyForKey(); // Check that menu is waiting for the key press - void registerKeyPress(byte keyCode); // Register the key press and trigger corresponding action - // Accepts GEM_KEY_NONE, GEM_KEY_UP, GEM_KEY_RIGHT, GEM_KEY_DOWN, GEM_KEY_LEFT, GEM_KEY_CANCEL, GEM_KEY_OK values - private: + bool readyForKey(); // Checks that menu is waiting for the key press + GEM_u8g2& registerKeyPress(byte keyCode); // Register the key press and trigger corresponding action + // Accepts GEM_KEY_NONE, GEM_KEY_UP, GEM_KEY_RIGHT, GEM_KEY_DOWN, GEM_KEY_LEFT, GEM_KEY_CANCEL, GEM_KEY_OK values + protected: U8G2& _u8g2; - byte _menuPointerType; - byte _menuItemsPerScreen; - byte _menuItemHeight; - byte _menuPageScreenTopOffset; - byte _menuValuesLeftOffset; - byte _menuItemFontSize; + GEMAppearance* _appearanceCurrent = nullptr; + GEMAppearance _appearance; + byte getMenuItemsPerScreen(); + byte getMenuItemFontSize(); FontSize _menuItemFont[2] = {{6,8},{4,6}}; FontFamiliesU8g2 _fontFamilies = {GEM_FONT_BIG, GEM_FONT_SMALL}; - bool _cyrillicEnabled = false; - byte _menuItemInsetOffset; - byte _menuItemTitleLength; - byte _menuItemValueLength; + bool _UTF8Enabled = false; + bool _invertKeysDuringEdit = false; + GEM_VIRTUAL byte getMenuItemTitleLength(); + GEM_VIRTUAL byte getMenuItemValueLength(); Splash _splash; uint16_t _splashDelay = 1000; - boolean _enableVersion = true; + bool _enableVersion = true; /* DRAW OPERATIONS */ GEMPage* _menuPageCurrent = nullptr; - void layoutMenu(); - void drawTitleBar(); - void printMenuItemString(const char* str, byte num, byte startPos = 0); - void printMenuItemTitle(const char* str, int offset = 0); - void printMenuItemValue(const char* str, int offset = 0, byte startPos = 0); - void printMenuItemFull(const char* str, int offset = 0); - byte getMenuItemInsetOffset(boolean forSprite = false); - byte getCurrentItemTopOffset(boolean withInsetOffset = true, boolean forSprite = false); - void printMenuItems(); - void drawMenuPointer(); - void drawScrollbar(); + void (*drawMenuCallback)() = nullptr; + GEM_VIRTUAL void drawTitleBar(); + GEM_VIRTUAL void printMenuItemString(const char* str, byte num, byte startPos = 0); + GEM_VIRTUAL void printMenuItemTitle(const char* str, int offset = 0); + GEM_VIRTUAL void printMenuItemValue(const char* str, int offset = 0, byte startPos = 0); + GEM_VIRTUAL void printMenuItemFull(const char* str, int offset = 0); + GEM_VIRTUAL byte getMenuItemInsetOffset(bool forSprite = false); + GEM_VIRTUAL byte getCurrentItemTopOffset(bool withInsetOffset = true, bool forSprite = false); + GEM_VIRTUAL void printMenuItems(); + GEM_VIRTUAL void drawMenuPointer(); + GEM_VIRTUAL void drawScrollbar(); /* MENU ITEMS NAVIGATION */ - void nextMenuItem(); - void prevMenuItem(); - void menuItemSelect(); + GEM_VIRTUAL void nextMenuItem(); + GEM_VIRTUAL void prevMenuItem(); + GEM_VIRTUAL void menuItemSelect(); /* VALUE EDIT */ - boolean _editValueMode; + bool _editValueMode; byte _editValueType; byte _editValueLength; byte _editValueCursorPosition; byte _editValueVirtualCursorPosition; char _valueString[GEM_STR_LEN]; int _valueSelectNum; - void enterEditValueMode(); - void checkboxToggle(); - void initEditValueCursor(); - void nextEditValueCursorPosition(); - void prevEditValueCursorPosition(); - void drawEditValueCursor(); - void nextEditValueDigit(); - void prevEditValueDigit(); - void drawEditValueDigit(byte code); - void nextEditValueSelect(); - void prevEditValueSelect(); - void saveEditValue(); - void cancelEditValue(); - void exitEditValue(); + GEM_VIRTUAL void enterEditValueMode(); + GEM_VIRTUAL void checkboxToggle(); + GEM_VIRTUAL void initEditValueCursor(); + GEM_VIRTUAL void nextEditValueCursorPosition(); + GEM_VIRTUAL void prevEditValueCursorPosition(); + GEM_VIRTUAL void drawEditValueCursor(); + GEM_VIRTUAL void nextEditValueDigit(); + GEM_VIRTUAL void prevEditValueDigit(); + GEM_VIRTUAL void drawEditValueDigit(byte code); + GEM_VIRTUAL void nextEditValueSelect(); + GEM_VIRTUAL void prevEditValueSelect(); + #ifdef GEM_SUPPORT_SPINNER + GEM_VIRTUAL void nextEditValueSpinner(); + GEM_VIRTUAL void prevEditValueSpinner(); + #endif + GEM_VIRTUAL void saveEditValue(); + GEM_VIRTUAL void cancelEditValue(); + GEM_VIRTUAL void exitEditValue(); char* trimString(char* str); /* KEY DETECTION */ diff --git a/src/config.h b/src/config.h index b684083..b1e7d49 100644 --- a/src/config.h +++ b/src/config.h @@ -1,23 +1,38 @@ -// AltSerialGraphicLCD support enabled by default. -// Can be disabled either by defining GEM_DISABLE_GLCD (via compiler flag or define) or manual edition here. -#ifndef GEM_DISABLE_GLCD -#include "config/enable-glcd.h" // Enable AltSerialGraphicLCD version of GEM +// AltSerialGraphicLCD support disabled by default (since GEM ver. 1.5). +// Can be enabled either by defining GEM_ENABLE_GLCD (via compiler flag or define) or manual edition here. +#define GEM_DISABLE_GLCD // Comment this line to enable AltSerialGraphicLCD support +#if !defined(GEM_DISABLE_GLCD) || defined(GEM_ENABLE_GLCD) +#include "config/enable-glcd.h" // Enable AltSerialGraphicLCD version of GEM #endif // U8g2 support enabled by default. // Can be disabled either by defining GEM_DISABLE_U8G2 (via compiler flag or define) or manual edition here. -#ifndef GEM_DISABLE_U8G2 -#include "config/enable-u8g2.h" // Enable U8g2 version of GEM +#if !defined(GEM_DISABLE_U8G2) +#include "config/enable-u8g2.h" // Enable U8g2 version of GEM #endif // Adafruit GFX support enabled by default. // Can be disabled either by defining GEM_DISABLE_ADAFRUIT_GFX (via compiler flag or define) or manual edition here. -#ifndef GEM_DISABLE_ADAFRUIT_GFX -#include "config/enable-adafruit-gfx.h" // Enable Adafruit GFX version of GEM +#if !defined(GEM_DISABLE_ADAFRUIT_GFX) +#include "config/enable-adafruit-gfx.h" // Enable Adafruit GFX version of GEM #endif // Support for editable float variables enabled by default. // Can be disabled either by defining GEM_DISABLE_FLOAT_EDIT (via compiler flag or define) or manual edition here. -#ifndef GEM_DISABLE_FLOAT_EDIT -#include "config/support-float-edit.h" // Support for editable float and double variables (option selects support them regardless of this setting) +#if !defined(GEM_DISABLE_FLOAT_EDIT) +#include "config/support-float-edit.h" // Support for editable float and double variables (option selects support them regardless of this setting) +#endif + +// Support for spinner (GEMSpinner) menu items enabled by default. +// Can be disabled either by defining GEM_DISABLE_SPINNER (via compiler flag or define) or manual edition here. +#if !defined(GEM_DISABLE_SPINNER) +#include "config/support-spinner.h" // Support for increment/decrement spinner menu items +#endif + +// Support for Advanced Mode is disabled by default. +// Advanced Mode provides additional means to modify, customize and extend functionality of GEM. +// Can be enabled either by defining GEM_ENABLE_ADVANCED_MODE (via compiler flag or define) or manual edition here. +#define GEM_DISABLE_ADVANCED_MODE // Comment this line to enable Advanced Mode +#if !defined(GEM_DISABLE_ADVANCED_MODE) || defined(GEM_ENABLE_ADVANCED_MODE) +#include "config/enable-advanced-mode.h" // Enable Advanced Mode #endif diff --git a/src/config/enable-advanced-mode.h b/src/config/enable-advanced-mode.h new file mode 100644 index 0000000..a14c933 --- /dev/null +++ b/src/config/enable-advanced-mode.h @@ -0,0 +1,3 @@ +#ifndef GEM_ENABLE_ADVANCED_MODE +#define GEM_ENABLE_ADVANCED_MODE +#endif \ No newline at end of file diff --git a/src/config/support-spinner.h b/src/config/support-spinner.h new file mode 100644 index 0000000..7a99c0e --- /dev/null +++ b/src/config/support-spinner.h @@ -0,0 +1,3 @@ +#ifndef GEM_SUPPORT_SPINNER +#define GEM_SUPPORT_SPINNER +#endif \ No newline at end of file diff --git a/src/constants.h b/src/constants.h index 5c71ed3..b4b59bb 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,6 +1,6 @@ /* GEM (a.k.a. Good Enough Menu) - Arduino library for creation of graphic multi-level menu with - editable menu items, such as variables (supports int, byte, float, double, boolean, char[17] data types) + editable menu items, such as variables (supports int, byte, float, double, bool, char[17] data types) and option selects. User-defined callback function can be specified to invoke when menu item is saved. Supports buttons that can invoke user-defined actions and create action-specific @@ -14,7 +14,7 @@ For documentation visit: https://github.com/Spirik/GEM - Copyright (c) 2018-2022 Alexander 'Spirik' Spiridonov + Copyright (c) 2018-2024 Alexander 'Spirik' Spiridonov This file is part of GEM library. @@ -32,8 +32,8 @@ along with this library. If not, see . */ -// Macro constant (alias) for current version of GEM library, printed on _splash screen -#define GEM_VER "1.4" +// Macro constant (alias) for current version of GEM library, printed on splash screen +#define GEM_VER "1.6" // Macro constant (alias) for supported length of the string (character sequence) variable of type char[GEM_STR_LEN] #define GEM_STR_LEN 17 @@ -53,8 +53,17 @@ #define GEM_VAL_INTEGER 0 // Associated variable is of type int #define GEM_VAL_BYTE 1 // Associated variable is of type byte #define GEM_VAL_CHAR 2 // Associated variable is of type char[GEM_STR_LEN] -#define GEM_VAL_BOOLEAN 3 // Associated variable is of type boolean -#define GEM_VAL_SELECT 4 // Associated variable is either of type int, byte or char[] with option select used to pick a predefined value from the list +#define GEM_VAL_BOOL 3 // Associated variable is of type bool +#define GEM_VAL_BOOLEAN GEM_VAL_BOOL +#define GEM_VAL_SELECT 4 // Associated variable is either of type int, byte, char[], float or double with option select used to pick a predefined value from the list // (note that char[] array should be big enough to hold select option with the longest value) #define GEM_VAL_FLOAT 5 // Associated variable is of type float #define GEM_VAL_DOUBLE 6 // Associated variable is of type double +#define GEM_VAL_SPINNER 7 // Associated variable is either of type int, byte, float or double with spinner to increment or decrement value with given step + +// Macro used internally to mark virtual functions in Advanced Mode +#ifdef GEM_ENABLE_ADVANCED_MODE +#define GEM_VIRTUAL virtual +#else +#define GEM_VIRTUAL +#endif \ No newline at end of file